2020-11-22 13:38:18 +01:00
/*
2022-02-19 20:13:47 +01:00
* Copyright ( c ) 2020 - 2022 , Andreas Kling < kling @ serenityos . org >
2020-11-22 13:38:18 +01:00
*
2021-04-22 01:24:48 -07:00
* SPDX - License - Identifier : BSD - 2 - Clause
2020-11-22 13:38:18 +01:00
*/
2020-12-05 20:10:39 +01:00
# include <LibWeb/Dump.h>
2020-11-22 13:38:18 +01:00
# include <LibWeb/Layout/BlockFormattingContext.h>
2020-11-22 15:53:01 +01:00
# include <LibWeb/Layout/Box.h>
2021-01-18 17:33:46 +01:00
# include <LibWeb/Layout/FlexFormattingContext.h>
2020-11-22 13:38:18 +01:00
# include <LibWeb/Layout/FormattingContext.h>
2022-08-23 10:36:27 +02:00
# include <LibWeb/Layout/GridFormattingContext.h>
2022-10-01 13:48:28 +02:00
# include <LibWeb/Layout/InitialContainingBlock.h>
2020-12-11 22:27:09 +01:00
# include <LibWeb/Layout/ReplacedBox.h>
2021-09-17 23:12:16 +02:00
# include <LibWeb/Layout/SVGFormattingContext.h>
# include <LibWeb/Layout/SVGSVGBox.h>
2021-01-01 18:55:47 +01:00
# include <LibWeb/Layout/TableBox.h>
# include <LibWeb/Layout/TableCellBox.h>
2020-11-22 13:38:18 +01:00
# include <LibWeb/Layout/TableFormattingContext.h>
namespace Web : : Layout {
2022-07-16 23:30:32 +02:00
FormattingContext : : FormattingContext ( Type type , LayoutState & state , Box const & context_box , FormattingContext * parent )
2021-10-15 01:25:12 +02:00
: m_type ( type )
, m_parent ( parent )
2021-10-15 23:15:12 +02:00
, m_context_box ( context_box )
2022-02-19 20:13:47 +01:00
, m_state ( state )
2020-11-22 13:38:18 +01:00
{
}
2022-03-14 13:21:51 -06:00
FormattingContext : : ~ FormattingContext ( ) = default ;
2020-11-22 13:38:18 +01:00
2022-10-06 16:51:53 +02:00
// https://developer.mozilla.org/en-US/docs/Web/Guide/CSS/Block_formatting_context
2022-04-01 20:58:27 +03:00
bool FormattingContext : : creates_block_formatting_context ( Box const & box )
2020-12-05 20:10:39 +01:00
{
2022-10-06 17:29:05 +02:00
// NOTE: Replaced elements never create a BFC.
if ( box . is_replaced_box ( ) )
return false ;
2022-11-25 06:44:33 +03:00
// display: table
if ( box . display ( ) . is_table_inside ( ) ) {
return false ;
}
// display: flex
if ( box . display ( ) . is_flex_inside ( ) ) {
return false ;
}
// display: grid
if ( box . display ( ) . is_grid_inside ( ) ) {
return false ;
}
2022-10-06 16:51:53 +02:00
// NOTE: This function uses MDN as a reference, not because it's authoritative,
// but because they've gathered all the conditions in one convenient location.
// The root element of the document (<html>).
2020-12-05 20:10:39 +01:00
if ( box . is_root_element ( ) )
return true ;
2022-10-06 16:51:53 +02:00
// Floats (elements where float isn't none).
2020-12-05 20:10:39 +01:00
if ( box . is_floating ( ) )
return true ;
2022-10-06 16:51:53 +02:00
// Absolutely positioned elements (elements where position is absolute or fixed).
2020-12-05 20:10:39 +01:00
if ( box . is_absolutely_positioned ( ) )
return true ;
2022-10-06 16:51:53 +02:00
// Inline-blocks (elements with display: inline-block).
if ( box . display ( ) . is_inline_block ( ) )
return true ;
// Table cells (elements with display: table-cell, which is the default for HTML table cells).
if ( box . display ( ) . is_table_cell ( ) )
2020-12-05 20:10:39 +01:00
return true ;
2022-10-06 16:51:53 +02:00
// Table captions (elements with display: table-caption, which is the default for HTML table captions).
if ( box . display ( ) . is_table_caption ( ) )
2020-12-05 20:10:39 +01:00
return true ;
2021-01-18 17:33:46 +01:00
2022-10-06 16:51:53 +02:00
// FIXME: Anonymous table cells implicitly created by the elements with display: table, table-row, table-row-group, table-header-group, table-footer-group
// (which is the default for HTML tables, table rows, table bodies, table headers, and table footers, respectively), or inline-table.
// Block elements where overflow has a value other than visible and clip.
2021-03-30 16:04:32 -04:00
CSS : : Overflow overflow_x = box . computed_values ( ) . overflow_x ( ) ;
if ( ( overflow_x ! = CSS : : Overflow : : Visible ) & & ( overflow_x ! = CSS : : Overflow : : Clip ) )
return true ;
CSS : : Overflow overflow_y = box . computed_values ( ) . overflow_y ( ) ;
if ( ( overflow_y ! = CSS : : Overflow : : Visible ) & & ( overflow_y ! = CSS : : Overflow : : Clip ) )
return true ;
2022-10-06 16:51:53 +02:00
// display: flow-root.
if ( box . display ( ) . is_flow_root_inside ( ) )
2021-10-06 17:57:44 +02:00
return true ;
2022-10-06 16:51:53 +02:00
// FIXME: Elements with contain: layout, content, or paint.
2021-10-06 17:57:44 +02:00
if ( box . parent ( ) ) {
2022-10-06 16:02:53 +02:00
auto parent_display = box . parent ( ) - > display ( ) ;
2022-10-06 16:51:53 +02:00
// Flex items (direct children of the element with display: flex or inline-flex) if they are neither flex nor grid nor table containers themselves.
2022-11-25 06:44:33 +03:00
if ( parent_display . is_flex_inside ( ) )
return true ;
2022-10-06 16:51:53 +02:00
// Grid items (direct children of the element with display: grid or inline-grid) if they are neither flex nor grid nor table containers themselves.
2022-11-25 06:44:33 +03:00
if ( parent_display . is_grid_inside ( ) )
return true ;
2021-01-18 17:33:46 +01:00
}
2022-10-06 16:51:53 +02:00
// FIXME: Multicol containers (elements where column-count or column-width isn't auto, including elements with column-count: 1).
// FIXME: column-span: all should always create a new formatting context, even when the column-span: all element isn't contained by a multicol container (Spec change, Chrome bug).
2020-12-05 20:10:39 +01:00
return false ;
}
2022-07-16 23:30:32 +02:00
OwnPtr < FormattingContext > FormattingContext : : create_independent_formatting_context_if_needed ( LayoutState & state , Box const & child_box )
2020-11-22 13:38:18 +01:00
{
2022-04-11 00:39:43 +02:00
if ( child_box . is_replaced_box ( ) & & ! child_box . can_have_children ( ) ) {
// NOTE: This is a bit strange.
// Basically, we create a pretend formatting context for replaced elements that does nothing.
// This allows other formatting contexts to treat them like elements that actually need inside layout
// without having separate code to handle replaced elements.
// FIXME: Find a better abstraction for this.
struct ReplacedFormattingContext : public FormattingContext {
2022-07-16 23:30:32 +02:00
ReplacedFormattingContext ( LayoutState & state , Box const & box )
2022-04-11 00:39:43 +02:00
: FormattingContext ( Type : : Block , state , box )
{
}
2022-11-23 17:46:10 +00:00
virtual CSSPixels automatic_content_height ( ) const override { return 0 ; } ;
2022-09-27 15:29:17 +02:00
virtual void run ( Box const & , LayoutMode , AvailableSpace const & ) override { }
2022-04-11 00:39:43 +02:00
} ;
return make < ReplacedFormattingContext > ( state , child_box ) ;
}
2021-10-06 21:42:42 +02:00
if ( ! child_box . can_have_children ( ) )
2021-10-17 18:24:07 +02:00
return { } ;
2021-09-17 23:12:16 +02:00
2022-10-06 16:02:53 +02:00
auto child_display = child_box . display ( ) ;
2021-10-06 17:57:44 +02:00
2021-10-17 18:24:07 +02:00
if ( is < SVGSVGBox > ( child_box ) )
2022-02-27 10:10:45 +01:00
return make < SVGFormattingContext > ( state , child_box , this ) ;
2021-09-23 14:24:04 +02:00
2021-10-17 18:24:07 +02:00
if ( child_display . is_flex_inside ( ) )
2022-02-27 10:10:45 +01:00
return make < FlexFormattingContext > ( state , child_box , this ) ;
2021-01-18 17:33:46 +01:00
2021-10-17 18:24:07 +02:00
if ( creates_block_formatting_context ( child_box ) )
2022-02-27 10:10:45 +01:00
return make < BlockFormattingContext > ( state , verify_cast < BlockContainer > ( child_box ) , this ) ;
2021-10-06 17:57:44 +02:00
2021-10-17 18:24:07 +02:00
if ( child_display . is_table_inside ( ) )
2022-02-27 10:10:45 +01:00
return make < TableFormattingContext > ( state , verify_cast < TableBox > ( child_box ) , this ) ;
2021-10-06 17:57:44 +02:00
2022-08-23 10:36:27 +02:00
if ( child_display . is_grid_inside ( ) ) {
2023-01-23 15:19:32 +01:00
return make < GridFormattingContext > ( state , child_box , this ) ;
2022-08-23 10:36:27 +02:00
}
2021-10-17 18:24:07 +02:00
VERIFY ( is_block_formatting_context ( ) ) ;
2022-12-11 01:27:42 +03:00
if ( child_box . children_are_inline ( ) )
return { } ;
2021-10-06 17:57:44 +02:00
2021-10-17 18:24:07 +02:00
// The child box is a block container that doesn't create its own BFC.
// It will be formatted by this BFC.
2022-02-21 16:53:38 +01:00
if ( ! child_display . is_flow_inside ( ) ) {
2022-12-06 01:12:49 +00:00
dbgln ( " FIXME: Child box doesn't create BFC, but inside is also not flow! display={} " , child_display . to_deprecated_string ( ) ) ;
2022-02-21 16:53:38 +01:00
// HACK: Instead of crashing, create a dummy formatting context that does nothing.
// FIXME: Remove this once it's no longer needed. It currently swallows problem with standalone
// table-related boxes that don't get fixed up by CSS anonymous table box generation.
struct DummyFormattingContext : public FormattingContext {
2022-07-16 23:30:32 +02:00
DummyFormattingContext ( LayoutState & state , Box const & box )
2022-02-21 16:53:38 +01:00
: FormattingContext ( Type : : Block , state , box )
{
}
2022-11-23 17:46:10 +00:00
virtual CSSPixels automatic_content_height ( ) const override { return 0 ; } ;
2022-09-27 15:29:17 +02:00
virtual void run ( Box const & , LayoutMode , AvailableSpace const & ) override { }
2022-02-21 16:53:38 +01:00
} ;
2022-02-27 10:10:45 +01:00
return make < DummyFormattingContext > ( state , child_box ) ;
2022-02-21 16:53:38 +01:00
}
2021-10-17 18:24:07 +02:00
VERIFY ( child_box . is_block_container ( ) ) ;
2022-02-21 16:53:38 +01:00
VERIFY ( child_display . is_flow_inside ( ) ) ;
2021-10-17 18:24:07 +02:00
return { } ;
}
2022-09-27 15:29:17 +02:00
OwnPtr < FormattingContext > FormattingContext : : layout_inside ( Box const & child_box , LayoutMode layout_mode , AvailableSpace const & available_space )
2021-10-17 18:24:07 +02:00
{
2022-07-20 18:13:11 +02:00
{
// OPTIMIZATION: If we're doing intrinsic sizing and `child_box` has definite size in both axes,
// we don't need to layout its insides. The size is resolvable without learning
// the metrics of whatever's inside the box.
auto const & used_values = m_state . get ( child_box ) ;
if ( layout_mode = = LayoutMode : : IntrinsicSizing
& & used_values . width_constraint = = SizeConstraint : : None
& & used_values . height_constraint = = SizeConstraint : : None
& & used_values . has_definite_width ( )
& & used_values . has_definite_height ( ) ) {
return nullptr ;
}
}
2021-10-17 18:24:07 +02:00
if ( ! child_box . can_have_children ( ) )
return { } ;
2022-02-27 10:10:45 +01:00
auto independent_formatting_context = create_independent_formatting_context_if_needed ( m_state , child_box ) ;
2021-10-17 18:24:07 +02:00
if ( independent_formatting_context )
2022-09-27 15:29:17 +02:00
independent_formatting_context - > run ( child_box , layout_mode , available_space ) ;
2021-10-17 18:24:07 +02:00
else
2022-09-27 15:29:17 +02:00
run ( child_box , layout_mode , available_space ) ;
2021-10-17 18:24:07 +02:00
return independent_formatting_context ;
2020-11-22 13:38:18 +01:00
}
2022-11-23 17:46:10 +00:00
CSSPixels FormattingContext : : greatest_child_width ( Box const & box )
2020-11-22 13:38:18 +01:00
{
2022-11-23 17:46:10 +00:00
CSSPixels max_width = 0 ;
2020-11-22 13:38:18 +01:00
if ( box . children_are_inline ( ) ) {
2022-10-06 17:28:00 +02:00
for ( auto & line_box : m_state . get ( box ) . line_boxes ) {
2022-03-18 14:44:36 +01:00
max_width = max ( max_width , line_box . width ( ) ) ;
2020-11-22 13:38:18 +01:00
}
} else {
2022-09-13 18:53:11 +02:00
box . for_each_child_of_type < Box > ( [ & ] ( Box const & child ) {
if ( ! child . is_absolutely_positioned ( ) )
2022-11-23 22:15:24 +03:00
max_width = max ( max_width , m_state . get ( child ) . margin_box_width ( ) ) ;
2020-11-22 13:38:18 +01:00
} ) ;
}
return max_width ;
}
2022-02-20 15:51:24 +01:00
FormattingContext : : ShrinkToFitResult FormattingContext : : calculate_shrink_to_fit_widths ( Box const & box )
2020-11-22 13:38:18 +01:00
{
2022-03-18 12:44:19 +01:00
return {
2022-07-08 00:40:53 +02:00
. preferred_width = calculate_max_content_width ( box ) ,
. preferred_minimum_width = calculate_min_content_width ( box ) ,
2022-03-18 12:44:19 +01:00
} ;
2020-11-22 13:38:18 +01:00
}
2022-11-23 17:46:10 +00:00
static CSSPixelSize solve_replaced_size_constraint ( LayoutState const & state , CSSPixels w , CSSPixels h , ReplacedBox const & box )
2020-12-11 22:27:09 +01:00
{
2020-12-12 00:20:31 +01:00
// 10.4 Minimum and maximum widths: 'min-width' and 'max-width'
2020-12-11 22:27:09 +01:00
2022-02-20 15:51:24 +01:00
auto const & containing_block = * box . containing_block ( ) ;
auto const & containing_block_state = state . get ( containing_block ) ;
2022-07-17 17:59:02 +02:00
auto width_of_containing_block = CSS : : Length : : make_px ( containing_block_state . content_width ( ) ) ;
auto height_of_containing_block = CSS : : Length : : make_px ( containing_block_state . content_height ( ) ) ;
2022-01-19 16:19:43 +00:00
2022-11-23 17:46:10 +00:00
CSSPixels specified_min_width = box . computed_values ( ) . min_width ( ) . is_auto ( ) ? 0 : box . computed_values ( ) . min_width ( ) . resolved ( box , width_of_containing_block ) . to_px ( box ) ;
CSSPixels specified_max_width = box . computed_values ( ) . max_width ( ) . is_none ( ) ? w : box . computed_values ( ) . max_width ( ) . resolved ( box , width_of_containing_block ) . to_px ( box ) ;
CSSPixels specified_min_height = box . computed_values ( ) . min_height ( ) . is_auto ( ) ? 0 : box . computed_values ( ) . min_height ( ) . resolved ( box , height_of_containing_block ) . to_px ( box ) ;
CSSPixels specified_max_height = box . computed_values ( ) . max_height ( ) . is_none ( ) ? h : box . computed_values ( ) . max_height ( ) . resolved ( box , height_of_containing_block ) . to_px ( box ) ;
2020-12-11 22:27:09 +01:00
2020-12-12 00:20:31 +01:00
auto min_width = min ( specified_min_width , specified_max_width ) ;
auto max_width = max ( specified_min_width , specified_max_width ) ;
auto min_height = min ( specified_min_height , specified_max_height ) ;
auto max_height = max ( specified_min_height , specified_max_height ) ;
2020-12-11 22:27:09 +01:00
2020-12-12 00:20:31 +01:00
if ( w > max_width )
2022-11-23 17:46:10 +00:00
return { w , max ( max_width * ( h / w ) , min_height ) } ;
2020-12-12 00:20:31 +01:00
if ( w < min_width )
2022-11-23 17:46:10 +00:00
return { max_width , min ( min_width * ( h / w ) , max_height ) } ;
2020-12-12 00:20:31 +01:00
if ( h > max_height )
2022-11-23 17:46:10 +00:00
return { max ( max_height * ( w / h ) , min_width ) , max_height } ;
2020-12-12 00:20:31 +01:00
if ( h < min_height )
2022-11-23 17:46:10 +00:00
return { min ( min_height * ( w / h ) , max_width ) , min_height } ;
2020-12-12 00:20:31 +01:00
if ( ( w > max_width & & h > max_height ) & & ( max_width / w < max_height / h ) )
2022-11-23 17:46:10 +00:00
return { max_width , max ( min_height , max_width * ( h / w ) ) } ;
2020-12-12 00:20:31 +01:00
if ( ( w > max_width & & h > max_height ) & & ( max_width / w > max_height / h ) )
2022-11-23 17:46:10 +00:00
return { max ( min_width , max_height * ( w / h ) ) , max_height } ;
2020-12-12 00:20:31 +01:00
if ( ( w < min_width & & h < min_height ) & & ( min_width / w < min_height / h ) )
2022-11-23 17:46:10 +00:00
return { min ( max_width , min_height * ( w / h ) ) , min_height } ;
2020-12-12 00:20:31 +01:00
if ( ( w < min_width & & h < min_height ) & & ( min_width / w > min_height / h ) )
2022-11-23 17:46:10 +00:00
return { min_width , min ( max_height , min_width * ( h / w ) ) } ;
2020-12-12 00:20:31 +01:00
if ( w < min_width & & h > max_height )
return { min_width , max_height } ;
if ( w > max_width & & h < min_height )
return { max_width , min_height } ;
return { w , h } ;
}
2020-12-11 22:27:09 +01:00
2022-03-01 23:21:24 +01:00
// https://www.w3.org/TR/CSS22/visudet.html#root-height
2023-01-23 14:45:52 +01:00
CSSPixels FormattingContext : : compute_auto_height_for_block_formatting_context_root ( Box const & root ) const
2022-03-01 23:21:24 +01:00
{
// 10.6.7 'Auto' heights for block formatting context roots
2022-11-23 17:46:10 +00:00
Optional < CSSPixels > top ;
Optional < CSSPixels > bottom ;
2021-04-17 09:09:57 +03:00
2022-03-01 23:21:24 +01:00
if ( root . children_are_inline ( ) ) {
2021-04-17 09:09:57 +03:00
// If it only has inline-level children, the height is the distance between
2021-10-28 19:17:23 +02:00
// the top content edge and the bottom of the bottommost line box.
2022-09-27 15:29:17 +02:00
auto const & line_boxes = m_state . get ( root ) . line_boxes ;
2021-10-28 19:17:23 +02:00
top = 0 ;
2022-03-28 21:02:13 +02:00
if ( ! line_boxes . is_empty ( ) )
2022-02-27 13:34:34 +01:00
bottom = line_boxes . last ( ) . bottom ( ) ;
2021-04-17 09:09:57 +03:00
} else {
// If it has block-level children, the height is the distance between
// the top margin-edge of the topmost block-level child box
// and the bottom margin-edge of the bottommost block-level child box.
2022-03-01 23:21:24 +01:00
root . for_each_child_of_type < Box > ( [ & ] ( Layout : : Box & child_box ) {
// Absolutely positioned children are ignored,
// and relatively positioned boxes are considered without their offset.
// Note that the child box may be an anonymous block box.
2021-04-17 09:09:57 +03:00
if ( child_box . is_absolutely_positioned ( ) )
return IterationDecision : : Continue ;
2022-03-01 23:21:24 +01:00
// FIXME: This doesn't look right.
if ( ( root . computed_values ( ) . overflow_y ( ) = = CSS : : Overflow : : Visible ) & & child_box . is_floating ( ) )
2021-04-17 09:09:57 +03:00
return IterationDecision : : Continue ;
2022-09-27 15:29:17 +02:00
auto const & child_box_state = m_state . get ( child_box ) ;
2022-02-20 15:51:24 +01:00
2022-11-23 17:46:10 +00:00
CSSPixels child_box_top = child_box_state . offset . y ( ) - child_box_state . margin_box_top ( ) ;
CSSPixels child_box_bottom = child_box_state . offset . y ( ) + child_box_state . content_height ( ) + child_box_state . margin_box_bottom ( ) ;
2021-04-17 09:09:57 +03:00
if ( ! top . has_value ( ) | | child_box_top < top . value ( ) )
top = child_box_top ;
if ( ! bottom . has_value ( ) | | child_box_bottom > bottom . value ( ) )
bottom = child_box_bottom ;
return IterationDecision : : Continue ;
} ) ;
2022-03-26 00:11:37 +01:00
}
2021-04-17 09:09:57 +03:00
2022-03-26 00:11:37 +01:00
// In addition, if the element has any floating descendants
// whose bottom margin edge is below the element's bottom content edge,
// then the height is increased to include those edges.
2022-09-27 15:29:17 +02:00
for ( auto * floating_box : m_state . get ( root ) . floating_descendants ( ) ) {
2022-09-14 15:39:19 +02:00
// NOTE: Floating box coordinates are relative to their own containing block,
// which may or may not be the BFC root.
2022-09-27 15:29:17 +02:00
auto margin_box = margin_box_rect_in_ancestor_coordinate_space ( * floating_box , root , m_state ) ;
2022-11-23 17:46:10 +00:00
CSSPixels floating_box_bottom_margin_edge = margin_box . bottom ( ) + 1 ;
2022-09-14 15:39:19 +02:00
if ( ! bottom . has_value ( ) | | floating_box_bottom_margin_edge > bottom . value ( ) )
bottom = floating_box_bottom_margin_edge ;
2022-09-09 23:56:55 +02:00
}
2021-04-17 09:09:57 +03:00
2022-11-23 17:46:10 +00:00
return max ( CSSPixels ( 0.0f ) , bottom . value_or ( 0 ) - top . value_or ( 0 ) ) ;
2021-04-17 09:09:57 +03:00
}
2021-10-14 23:07:53 +02:00
// 10.3.2 Inline, replaced elements, https://www.w3.org/TR/CSS22/visudet.html#inline-replaced-width
2022-11-23 17:46:10 +00:00
CSSPixels FormattingContext : : tentative_width_for_replaced_element ( LayoutState const & state , ReplacedBox const & box , CSS : : Size const & computed_width , AvailableSpace const & available_space )
2020-12-12 00:20:31 +01:00
{
2022-09-24 14:41:18 +02:00
// Treat percentages of indefinite containing block widths as 0 (the initial width).
if ( computed_width . is_percentage ( ) & & ! state . get ( * box . containing_block ( ) ) . has_definite_width ( ) )
return 0 ;
auto height_of_containing_block = CSS : : Length : : make_px ( containing_block_height_for ( box , state ) ) ;
2022-11-03 18:46:50 +01:00
auto computed_height = should_treat_height_as_auto ( box , available_space ) ? CSS : : Size : : make_auto ( ) : box . computed_values ( ) . height ( ) ;
2020-12-11 22:27:09 +01:00
2022-11-23 17:46:10 +00:00
CSSPixels used_width = computed_width . resolved ( box , CSS : : Length : : make_px ( available_space . width . to_px ( ) ) ) . to_px ( box ) ;
2020-12-11 22:27:09 +01:00
// If 'height' and 'width' both have computed values of 'auto' and the element also has an intrinsic width,
// then that intrinsic width is the used value of 'width'.
2021-10-14 23:07:53 +02:00
if ( computed_height . is_auto ( ) & & computed_width . is_auto ( ) & & box . has_intrinsic_width ( ) )
return box . intrinsic_width ( ) . value ( ) ;
2020-12-11 22:27:09 +01:00
// If 'height' and 'width' both have computed values of 'auto' and the element has no intrinsic width,
// but does have an intrinsic height and intrinsic ratio;
// or if 'width' has a computed value of 'auto',
// 'height' has some other computed value, and the element does have an intrinsic ratio; then the used value of 'width' is:
//
// (used height) * (intrinsic ratio)
2021-10-14 23:07:53 +02:00
if ( ( computed_height . is_auto ( ) & & computed_width . is_auto ( ) & & ! box . has_intrinsic_width ( ) & & box . has_intrinsic_height ( ) & & box . has_intrinsic_aspect_ratio ( ) )
2022-07-11 12:28:35 +02:00
| | ( computed_width . is_auto ( ) & & ! computed_height . is_auto ( ) & & box . has_intrinsic_aspect_ratio ( ) ) ) {
2022-09-27 15:29:17 +02:00
return compute_height_for_replaced_element ( state , box , available_space ) * box . intrinsic_aspect_ratio ( ) . value ( ) ;
2020-12-11 22:27:09 +01:00
}
2021-10-14 23:07:53 +02:00
// If 'height' and 'width' both have computed values of 'auto' and the element has an intrinsic ratio but no intrinsic height or width,
// then the used value of 'width' is undefined in CSS 2.2. However, it is suggested that, if the containing block's width does not itself
// depend on the replaced element's width, then the used value of 'width' is calculated from the constraint equation used for block-level,
// non-replaced elements in normal flow.
2020-12-11 22:27:09 +01:00
2021-10-14 23:07:53 +02:00
// Otherwise, if 'width' has a computed value of 'auto', and the element has an intrinsic width, then that intrinsic width is the used value of 'width'.
if ( computed_width . is_auto ( ) & & box . has_intrinsic_width ( ) )
return box . intrinsic_width ( ) . value ( ) ;
// Otherwise, if 'width' has a computed value of 'auto', but none of the conditions above are met, then the used value of 'width' becomes 300px.
// If 300px is too wide to fit the device, UAs should use the width of the largest rectangle that has a 2:1 ratio and fits the device instead.
if ( computed_width . is_auto ( ) )
return 300 ;
2020-12-11 22:27:09 +01:00
return used_width ;
}
2022-09-27 15:29:17 +02:00
void FormattingContext : : compute_width_for_absolutely_positioned_element ( Box const & box , AvailableSpace const & available_space )
2021-01-06 18:18:46 +01:00
{
if ( is < ReplacedBox > ( box ) )
2022-09-27 15:29:17 +02:00
compute_width_for_absolutely_positioned_replaced_element ( verify_cast < ReplacedBox > ( box ) , available_space ) ;
2021-01-06 18:18:46 +01:00
else
2022-09-27 15:29:17 +02:00
compute_width_for_absolutely_positioned_non_replaced_element ( box , available_space ) ;
2021-01-06 18:18:46 +01:00
}
2023-01-06 21:05:18 +01:00
void FormattingContext : : compute_height_for_absolutely_positioned_element ( Box const & box , AvailableSpace const & available_space , BeforeOrAfterInsideLayout before_or_after_inside_layout )
2021-01-06 18:18:46 +01:00
{
if ( is < ReplacedBox > ( box ) )
2023-01-06 21:05:18 +01:00
compute_height_for_absolutely_positioned_replaced_element ( static_cast < ReplacedBox const & > ( box ) , available_space , before_or_after_inside_layout ) ;
2021-01-06 18:18:46 +01:00
else
2023-01-06 21:05:18 +01:00
compute_height_for_absolutely_positioned_non_replaced_element ( box , available_space , before_or_after_inside_layout ) ;
2021-01-06 18:18:46 +01:00
}
2022-11-23 17:46:10 +00:00
CSSPixels FormattingContext : : compute_width_for_replaced_element ( LayoutState const & state , ReplacedBox const & box , AvailableSpace const & available_space )
2020-12-11 22:27:09 +01:00
{
2020-12-12 00:20:31 +01:00
// 10.3.4 Block-level, replaced elements in normal flow...
// 10.3.2 Inline, replaced elements
auto zero_value = CSS : : Length : : make_px ( 0 ) ;
2022-09-27 15:29:17 +02:00
auto width_of_containing_block_as_length = CSS : : Length : : make_px ( available_space . width . to_px ( ) ) ;
2020-12-11 22:27:09 +01:00
2022-09-13 17:42:39 +02:00
auto margin_left = box . computed_values ( ) . margin ( ) . left ( ) . resolved ( box , width_of_containing_block_as_length ) . resolved ( box ) ;
auto margin_right = box . computed_values ( ) . margin ( ) . right ( ) . resolved ( box , width_of_containing_block_as_length ) . resolved ( box ) ;
2020-12-12 00:20:31 +01:00
// A computed value of 'auto' for 'margin-left' or 'margin-right' becomes a used value of '0'.
if ( margin_left . is_auto ( ) )
margin_left = zero_value ;
if ( margin_right . is_auto ( ) )
margin_right = zero_value ;
2022-11-03 18:46:50 +01:00
auto computed_width = should_treat_width_as_auto ( box , available_space ) ? CSS : : Size : : make_auto ( ) : box . computed_values ( ) . width ( ) ;
2020-12-12 00:20:31 +01:00
// 1. The tentative used width is calculated (without 'min-width' and 'max-width')
2022-09-27 15:29:17 +02:00
auto used_width = tentative_width_for_replaced_element ( state , box , computed_width , available_space ) ;
2020-12-12 00:20:31 +01:00
// 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'.
2022-07-11 12:28:35 +02:00
auto computed_max_width = box . computed_values ( ) . max_width ( ) ;
2022-09-25 15:48:23 +02:00
if ( ! computed_max_width . is_none ( ) ) {
2022-07-11 12:28:35 +02:00
if ( used_width > computed_max_width . resolved ( box , width_of_containing_block_as_length ) . to_px ( box ) ) {
2022-09-27 15:29:17 +02:00
used_width = tentative_width_for_replaced_element ( state , box , computed_max_width , available_space ) ;
2020-12-12 00:20:31 +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'.
2022-07-11 12:28:35 +02:00
auto computed_min_width = box . computed_values ( ) . min_width ( ) ;
if ( ! computed_min_width . is_auto ( ) ) {
if ( used_width < computed_min_width . resolved ( box , width_of_containing_block_as_length ) . to_px ( box ) ) {
2022-09-27 15:29:17 +02:00
used_width = tentative_width_for_replaced_element ( state , box , computed_min_width , available_space ) ;
2020-12-12 00:20:31 +01:00
}
}
return used_width ;
}
2021-10-14 23:15:36 +02:00
// 10.6.2 Inline replaced elements, block-level replaced elements in normal flow, 'inline-block' replaced elements in normal flow and floating replaced elements
// https://www.w3.org/TR/CSS22/visudet.html#inline-replaced-height
2022-11-23 17:46:10 +00:00
CSSPixels FormattingContext : : tentative_height_for_replaced_element ( LayoutState const & state , ReplacedBox const & box , CSS : : Size const & computed_height , AvailableSpace const & available_space )
2020-12-12 00:20:31 +01:00
{
2020-12-11 22:27:09 +01:00
// If 'height' and 'width' both have computed values of 'auto' and the element also has
// an intrinsic height, then that intrinsic height is the used value of 'height'.
2022-11-03 18:46:50 +01:00
if ( should_treat_width_as_auto ( box , available_space ) & & should_treat_height_as_auto ( box , available_space ) & & box . has_intrinsic_height ( ) )
2021-10-14 23:15:36 +02:00
return box . intrinsic_height ( ) . value ( ) ;
2020-12-11 22:27:09 +01:00
2021-10-14 23:15:36 +02:00
// Otherwise, if 'height' has a computed value of 'auto', and the element has an intrinsic ratio then the used value of 'height' is:
//
// (used width) / (intrinsic ratio)
if ( computed_height . is_auto ( ) & & box . has_intrinsic_aspect_ratio ( ) )
2022-09-27 15:29:17 +02:00
return compute_width_for_replaced_element ( state , box , available_space ) / box . intrinsic_aspect_ratio ( ) . value ( ) ;
2021-10-14 23:15:36 +02:00
// Otherwise, if 'height' has a computed value of 'auto', and the element has an intrinsic height, then that intrinsic height is the used value of 'height'.
if ( computed_height . is_auto ( ) & & box . has_intrinsic_height ( ) )
return box . intrinsic_height ( ) . value ( ) ;
// Otherwise, if 'height' has a computed value of 'auto', but none of the conditions above are met,
// then the used value of 'height' must be set to the height of the largest rectangle that has a 2:1 ratio, has a height not greater than 150px,
// and has a width not greater than the device width.
if ( computed_height . is_auto ( ) )
return 150 ;
2022-09-27 15:29:17 +02:00
return computed_height . resolved ( box , CSS : : Length : : make_px ( available_space . height . to_px ( ) ) ) . to_px ( box ) ;
2020-12-11 22:27:09 +01:00
}
2022-11-23 17:46:10 +00:00
CSSPixels FormattingContext : : compute_height_for_replaced_element ( LayoutState const & state , ReplacedBox const & box , AvailableSpace const & available_space )
2020-12-12 00:20:31 +01:00
{
// 10.6.2 Inline replaced elements, block-level replaced elements in normal flow,
// 'inline-block' replaced elements in normal flow and floating replaced elements
2022-09-27 15:29:17 +02:00
auto width_of_containing_block_as_length = CSS : : Length : : make_px ( available_space . width . to_px ( ) ) ;
auto height_of_containing_block_as_length = CSS : : Length : : make_px ( available_space . height . to_px ( ) ) ;
2022-11-03 18:46:50 +01:00
auto computed_width = should_treat_width_as_auto ( box , available_space ) ? CSS : : Size : : make_auto ( ) : box . computed_values ( ) . width ( ) ;
auto computed_height = should_treat_height_as_auto ( box , available_space ) ? CSS : : Size : : make_auto ( ) : box . computed_values ( ) . height ( ) ;
2020-12-12 00:20:31 +01:00
2022-11-23 17:46:10 +00:00
CSSPixels used_height = tentative_height_for_replaced_element ( state , box , computed_height , available_space ) ;
2020-12-12 00:20:31 +01:00
2022-07-11 12:28:35 +02:00
if ( computed_width . is_auto ( ) & & computed_height . is_auto ( ) & & box . has_intrinsic_aspect_ratio ( ) ) {
2022-11-23 17:46:10 +00:00
CSSPixels w = tentative_width_for_replaced_element ( state , box , computed_width , available_space ) ;
CSSPixels h = used_height ;
2022-02-20 15:51:24 +01:00
used_height = solve_replaced_size_constraint ( state , w , h , box ) . height ( ) ;
2020-12-12 00:20:31 +01:00
}
return used_height ;
}
2022-09-27 15:29:17 +02:00
void FormattingContext : : compute_width_for_absolutely_positioned_non_replaced_element ( Box const & box , AvailableSpace const & available_space )
2021-01-06 18:18:46 +01:00
{
2022-09-27 15:29:17 +02:00
auto width_of_containing_block = available_space . width . to_px ( ) ;
2022-07-09 15:17:47 +02:00
auto width_of_containing_block_as_length = CSS : : Length : : make_px ( width_of_containing_block ) ;
2021-01-06 18:18:46 +01:00
auto & computed_values = box . computed_values ( ) ;
auto zero_value = CSS : : Length : : make_px ( 0 ) ;
auto margin_left = CSS : : Length : : make_auto ( ) ;
auto margin_right = CSS : : Length : : make_auto ( ) ;
2022-04-01 20:58:27 +03:00
auto const border_left = computed_values . border_left ( ) . width ;
auto const border_right = computed_values . border_right ( ) . width ;
2022-09-13 17:42:39 +02:00
auto const padding_left = computed_values . padding ( ) . left ( ) . resolved ( box , width_of_containing_block_as_length ) . to_px ( box ) ;
auto const padding_right = computed_values . padding ( ) . right ( ) . resolved ( box , width_of_containing_block_as_length ) . to_px ( box ) ;
2021-01-06 18:18:46 +01:00
2022-04-01 20:58:27 +03:00
auto try_compute_width = [ & ] ( auto const & a_width ) {
2022-09-13 17:42:39 +02:00
margin_left = computed_values . margin ( ) . left ( ) . resolved ( box , width_of_containing_block_as_length ) . resolved ( box ) ;
margin_right = computed_values . margin ( ) . right ( ) . resolved ( box , width_of_containing_block_as_length ) . resolved ( box ) ;
2021-01-06 18:18:46 +01:00
2022-09-13 17:42:39 +02:00
auto left = computed_values . inset ( ) . left ( ) . resolved ( box , width_of_containing_block_as_length ) . resolved ( box ) ;
auto right = computed_values . inset ( ) . right ( ) . resolved ( box , width_of_containing_block_as_length ) . resolved ( box ) ;
2021-01-06 18:18:46 +01:00
auto width = a_width ;
auto solve_for_left = [ & ] {
2022-11-23 17:46:10 +00:00
return CSS : : Length : : make_px ( width_of_containing_block - margin_left . to_px ( box ) - border_left - padding_left - width . to_px ( box ) - padding_right - border_right - margin_right . to_px ( box ) - right . to_px ( box ) ) ;
2021-01-06 18:18:46 +01:00
} ;
auto solve_for_width = [ & ] {
2022-11-23 17:46:10 +00:00
return CSS : : Length : : make_px ( width_of_containing_block - left . to_px ( box ) - margin_left . to_px ( box ) - border_left - padding_left - padding_right - border_right - margin_right . to_px ( box ) - right . to_px ( box ) ) ;
2021-01-06 18:18:46 +01:00
} ;
auto solve_for_right = [ & ] {
2022-11-23 17:46:10 +00:00
return CSS : : Length : : make_px ( width_of_containing_block - left . to_px ( box ) - margin_left . to_px ( box ) - border_left - padding_left - width . to_px ( box ) - padding_right - border_right - margin_right . to_px ( box ) ) ;
2021-01-06 18:18:46 +01: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 ( ) )
margin_left = CSS : : Length : : make_px ( 0 ) ;
if ( margin_right . is_auto ( ) )
margin_right = CSS : : Length : : make_px ( 0 ) ;
// 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.
left = CSS : : Length : : make_px ( 0 ) ;
goto Rule3 ;
}
if ( ! left . is_auto ( ) & & ! width . is_auto ( ) & & ! right . is_auto ( ) ) {
// FIXME: This should be solved in a more complicated way.
return width ;
}
if ( margin_left . is_auto ( ) )
margin_left = CSS : : Length : : make_px ( 0 ) ;
if ( margin_right . is_auto ( ) )
margin_right = CSS : : Length : : make_px ( 0 ) ;
// 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_widths ( box ) ;
auto available_width = solve_for_width ( ) ;
2022-11-23 17:46:10 +00:00
width = CSS : : Length : : make_px ( min ( max ( result . preferred_minimum_width , available_width . to_px ( box ) ) , result . preferred_width ) ) ;
2022-11-16 03:57:38 +03:00
left = solve_for_left ( ) ;
2021-01-06 18:18:46 +01: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 ( ) ;
}
// 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_widths ( box ) ;
auto available_width = solve_for_width ( ) ;
2022-11-23 17:46:10 +00:00
width = CSS : : Length : : make_px ( min ( max ( result . preferred_minimum_width , available_width . to_px ( box ) ) , result . preferred_width ) ) ;
2021-01-06 18:18:46 +01:00
right = solve_for_right ( ) ;
}
// 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 ( ) ;
}
// 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 ( ) ;
}
return width ;
} ;
// 1. The tentative used width is calculated (without 'min-width' and 'max-width')
2022-11-21 22:17:24 +03:00
auto used_width = try_compute_width ( calculate_inner_width ( box , available_space . width , computed_values . width ( ) ) ) ;
2021-01-06 18:18:46 +01:00
// 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'.
2022-09-25 15:48:23 +02:00
if ( ! computed_values . max_width ( ) . is_none ( ) ) {
2022-11-21 22:17:24 +03:00
auto max_width = calculate_inner_width ( box , available_space . width , computed_values . max_width ( ) ) ;
2022-09-25 15:48:23 +02:00
if ( used_width . to_px ( box ) > max_width . to_px ( box ) ) {
used_width = try_compute_width ( max_width ) ;
2021-01-06 18:18:46 +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'.
2022-09-25 15:48:23 +02:00
if ( ! computed_values . min_width ( ) . is_auto ( ) ) {
2022-11-21 22:17:24 +03:00
auto min_width = calculate_inner_width ( box , available_space . width , computed_values . min_width ( ) ) ;
2022-09-25 15:48:23 +02:00
if ( used_width . to_px ( box ) < min_width . to_px ( box ) ) {
used_width = try_compute_width ( min_width ) ;
2021-01-06 18:18:46 +01:00
}
}
2022-07-09 15:17:47 +02:00
auto & box_state = m_state . get_mutable ( box ) ;
2022-07-17 17:59:02 +02:00
box_state . set_content_width ( used_width . to_px ( box ) ) ;
2021-01-06 18:18:46 +01:00
2022-02-20 15:51:24 +01:00
box_state . margin_left = margin_left . to_px ( box ) ;
box_state . margin_right = margin_right . to_px ( box ) ;
box_state . border_left = border_left ;
box_state . border_right = border_right ;
box_state . padding_left = padding_left ;
box_state . padding_right = padding_right ;
2021-01-06 18:18:46 +01:00
}
2022-09-27 15:29:17 +02:00
void FormattingContext : : compute_width_for_absolutely_positioned_replaced_element ( ReplacedBox const & box , AvailableSpace const & available_space )
2021-01-06 18:18:46 +01:00
{
2021-01-06 19:16:40 +01:00
// 10.3.8 Absolutely positioned, replaced elements
// The used value of 'width' is determined as for inline replaced elements.
2022-02-20 15:51:24 +01:00
// FIXME: This const_cast is gross.
const_cast < ReplacedBox & > ( box ) . prepare_for_replaced_layout ( ) ;
2022-11-04 20:32:50 +00:00
m_state . get_mutable ( box ) . set_content_width ( compute_width_for_replaced_element ( m_state , box , available_space ) ) ;
2021-01-06 18:18:46 +01:00
}
2022-10-11 19:04:09 +02:00
// https://drafts.csswg.org/css-position-3/#abs-non-replaced-height
2023-01-06 21:05:18 +01:00
void FormattingContext : : compute_height_for_absolutely_positioned_non_replaced_element ( Box const & box , AvailableSpace const & available_space , BeforeOrAfterInsideLayout before_or_after_inside_layout )
2021-01-06 18:18:46 +01:00
{
2022-10-11 19:04:09 +02:00
// 5.3. The Height Of Absolutely Positioned, Non-Replaced Elements
2022-03-05 16:02:53 +01:00
2022-10-11 19:04:09 +02:00
// For absolutely positioned elements, the used values of the vertical dimensions must satisfy this constraint:
// top + margin-top + border-top-width + padding-top + height + padding-bottom + border-bottom-width + margin-bottom + bottom = height of containing block
2023-01-06 21:05:18 +01:00
// NOTE: This function is called twice: both before and after inside layout.
// In the before pass, if it turns out we need the automatic height of the box, we abort these steps.
// This allows the box to retain an indefinite height from the perspective of inside layout.
2022-10-11 19:04:09 +02:00
auto margin_top = box . computed_values ( ) . margin ( ) . top ( ) ;
auto margin_bottom = box . computed_values ( ) . margin ( ) . bottom ( ) ;
auto top = box . computed_values ( ) . inset ( ) . top ( ) ;
auto bottom = box . computed_values ( ) . inset ( ) . bottom ( ) ;
auto height = box . computed_values ( ) . height ( ) ;
2022-07-26 01:30:50 +02:00
2022-10-12 11:14:45 +02:00
auto width_of_containing_block = containing_block_width_for ( box ) ;
auto width_of_containing_block_as_length = CSS : : Length : : make_px ( width_of_containing_block ) ;
2022-09-27 15:29:17 +02:00
auto height_of_containing_block = available_space . height . to_px ( ) ;
2022-07-09 15:17:47 +02:00
auto height_of_containing_block_as_length = CSS : : Length : : make_px ( height_of_containing_block ) ;
2021-01-06 18:18:46 +01:00
2022-10-12 10:55:52 +02:00
auto solve_for = [ & ] ( CSS : : Length length ) {
return CSS : : Length : : make_px (
2022-10-11 19:04:09 +02:00
height_of_containing_block
2022-10-12 10:55:52 +02:00
- top . resolved ( box , height_of_containing_block_as_length ) . to_px ( box )
2022-10-12 11:14:45 +02:00
- margin_top . resolved ( box , width_of_containing_block_as_length ) . to_px ( box )
2022-10-11 19:04:09 +02:00
- box . computed_values ( ) . border_top ( ) . width
2022-10-12 11:14:45 +02:00
- box . computed_values ( ) . padding ( ) . top ( ) . resolved ( box , width_of_containing_block_as_length ) . to_px ( box )
2022-10-11 19:04:09 +02:00
- height . resolved ( box , height_of_containing_block_as_length ) . to_px ( box )
2022-10-12 11:14:45 +02:00
- box . computed_values ( ) . padding ( ) . bottom ( ) . resolved ( box , width_of_containing_block_as_length ) . to_px ( box )
2022-10-11 19:04:09 +02:00
- box . computed_values ( ) . border_bottom ( ) . width
2022-10-12 11:14:45 +02:00
- margin_bottom . resolved ( box , width_of_containing_block_as_length ) . to_px ( box )
2022-10-12 10:55:52 +02:00
- bottom . resolved ( box , height_of_containing_block_as_length ) . to_px ( box )
+ length . to_px ( box ) ) ;
} ;
auto solve_for_top = [ & ] {
top = solve_for ( top . resolved ( box , height_of_containing_block_as_length ) ) ;
2022-10-11 19:04:09 +02:00
} ;
auto solve_for_bottom = [ & ] {
2022-10-12 10:55:52 +02:00
bottom = solve_for ( bottom . resolved ( box , height_of_containing_block_as_length ) ) ;
2022-10-11 19:04:09 +02:00
} ;
2022-07-26 01:30:50 +02:00
2022-10-11 19:04:09 +02:00
auto solve_for_height = [ & ] {
2022-10-12 10:55:52 +02:00
height = CSS : : Size : : make_length ( solve_for ( height . resolved ( box , height_of_containing_block_as_length ) ) ) ;
2022-10-11 19:04:09 +02:00
} ;
2021-01-06 18:18:46 +01:00
2022-10-11 19:04:09 +02:00
auto solve_for_margin_top = [ & ] {
2022-10-12 11:14:45 +02:00
margin_top = solve_for ( margin_top . resolved ( box , width_of_containing_block_as_length ) ) ;
2022-10-11 19:04:09 +02:00
} ;
2021-01-06 18:18:46 +01:00
2022-10-11 19:04:09 +02:00
auto solve_for_margin_bottom = [ & ] {
2022-10-12 11:14:45 +02:00
margin_bottom = solve_for ( margin_bottom . resolved ( box , width_of_containing_block_as_length ) ) ;
2022-10-11 19:04:09 +02:00
} ;
auto solve_for_margin_top_and_margin_bottom = [ & ] {
2022-10-12 11:14:45 +02:00
auto remainder = solve_for ( CSS : : Length : : make_px ( margin_top . resolved ( box , width_of_containing_block_as_length ) . to_px ( box ) + margin_bottom . resolved ( box , width_of_containing_block_as_length ) . to_px ( box ) ) ) . to_px ( box ) ;
2022-10-11 19:04:09 +02:00
margin_top = CSS : : Length : : make_px ( remainder / 2 ) ;
margin_bottom = CSS : : Length : : make_px ( remainder / 2 ) ;
} ;
// If all three of top, height, and bottom are auto:
if ( top . is_auto ( ) & & height . is_auto ( ) & & bottom . is_auto ( ) ) {
2023-01-06 21:05:18 +01:00
// (If we haven't done inside layout yet, we can't compute the auto height.)
if ( before_or_after_inside_layout = = BeforeOrAfterInsideLayout : : Before )
return ;
2022-10-11 19:04:09 +02:00
// First set any auto values for margin-top and margin-bottom to 0,
if ( margin_top . is_auto ( ) )
margin_top = CSS : : Length : : make_px ( 0 ) ;
if ( margin_bottom . is_auto ( ) )
margin_bottom = CSS : : Length : : make_px ( 0 ) ;
// then set top to the static position,
auto static_position = calculate_static_position ( box ) ;
top = CSS : : Length : : make_px ( static_position . y ( ) ) ;
// and finally apply rule number three below.
2023-01-23 14:45:52 +01:00
height = CSS : : Size : : make_px ( compute_auto_height_for_block_formatting_context_root ( box ) ) ;
2022-10-11 19:04:09 +02:00
solve_for_bottom ( ) ;
2022-03-05 16:02:53 +01:00
}
2022-10-11 19:04:09 +02:00
// If none of the three are auto:
else if ( ! top . is_auto ( ) & & ! height . is_auto ( ) & & ! bottom . is_auto ( ) ) {
// If both margin-top and margin-bottom are auto,
if ( margin_top . is_auto ( ) & & margin_bottom . is_auto ( ) ) {
// solve the equation under the extra constraint that the two margins get equal values.
solve_for_margin_top_and_margin_bottom ( ) ;
}
// If one of margin-top or margin-bottom is auto,
else if ( margin_top . is_auto ( ) | | margin_bottom . is_auto ( ) ) {
// solve the equation for that value.
if ( margin_top . is_auto ( ) )
solve_for_margin_top ( ) ;
else
solve_for_margin_bottom ( ) ;
}
// If the values are over-constrained,
else {
// ignore the value for bottom and solve for that value.
solve_for_bottom ( ) ;
}
2021-04-17 09:09:57 +03:00
}
2022-10-11 19:04:09 +02:00
// Otherwise,
else {
// set auto values for margin-top and margin-bottom to 0,
if ( margin_top . is_auto ( ) )
margin_top = CSS : : Length : : make_px ( 0 ) ;
if ( margin_bottom . is_auto ( ) )
margin_bottom = CSS : : Length : : make_px ( 0 ) ;
// and pick one of the following six rules that apply.
// 1. If top and height are auto and bottom is not auto,
if ( top . is_auto ( ) & & height . is_auto ( ) & & ! bottom . is_auto ( ) ) {
2023-01-06 21:05:18 +01:00
// (If we haven't done inside layout yet, we can't compute the auto height.)
if ( before_or_after_inside_layout = = BeforeOrAfterInsideLayout : : Before )
return ;
2022-10-11 19:04:09 +02:00
// then the height is based on the Auto heights for block formatting context roots,
2023-01-23 14:45:52 +01:00
height = CSS : : Size : : make_px ( compute_auto_height_for_block_formatting_context_root ( box ) ) ;
2022-10-11 19:04:09 +02:00
// and solve for top.
solve_for_top ( ) ;
}
// 2. If top and bottom are auto and height is not auto,
else if ( top . is_auto ( ) & & bottom . is_auto ( ) & & ! height . is_auto ( ) ) {
// then set top to the static position,
top = CSS : : Length : : make_px ( calculate_static_position ( box ) . y ( ) ) ;
// then solve for bottom.
solve_for_bottom ( ) ;
}
// 3. If height and bottom are auto and top is not auto,
else if ( height . is_auto ( ) & & bottom . is_auto ( ) & & ! top . is_auto ( ) ) {
2023-01-06 21:05:18 +01:00
// (If we haven't done inside layout yet, we can't compute the auto height.)
if ( before_or_after_inside_layout = = BeforeOrAfterInsideLayout : : Before )
return ;
2022-10-11 19:04:09 +02:00
// then the height is based on the Auto heights for block formatting context roots,
2023-01-23 14:45:52 +01:00
height = CSS : : Size : : make_px ( compute_auto_height_for_block_formatting_context_root ( box ) ) ;
2022-10-11 19:04:09 +02:00
// and solve for bottom.
solve_for_bottom ( ) ;
}
// 4. If top is auto, height and bottom are not auto,
2022-10-12 00:27:20 -04:00
else if ( top . is_auto ( ) & & ! height . is_auto ( ) & & ! bottom . is_auto ( ) ) {
2022-10-11 19:04:09 +02:00
// then solve for top.
solve_for_top ( ) ;
}
// 5. If height is auto, top and bottom are not auto,
2022-10-12 00:27:20 -04:00
else if ( height . is_auto ( ) & & ! top . is_auto ( ) & & ! bottom . is_auto ( ) ) {
2022-10-11 19:04:09 +02:00
// then solve for height.
solve_for_height ( ) ;
}
// 6. If bottom is auto, top and height are not auto,
2022-10-12 00:27:20 -04:00
else if ( bottom . is_auto ( ) & & ! top . is_auto ( ) & & ! height . is_auto ( ) ) {
2022-10-11 19:04:09 +02:00
// then solve for bottom.
solve_for_bottom ( ) ;
}
2021-03-26 17:01:44 -04:00
}
2022-10-11 22:31:51 +02:00
auto used_height = height . resolved ( box , height_of_containing_block_as_length ) . to_px ( box ) ;
auto const & computed_min_height = box . computed_values ( ) . min_height ( ) ;
auto const & computed_max_height = box . computed_values ( ) . max_height ( ) ;
if ( ! computed_max_height . is_none ( ) )
used_height = min ( used_height , computed_max_height . resolved ( box , height_of_containing_block_as_length ) . resolved ( box ) . to_px ( box ) ) ;
if ( ! computed_min_height . is_auto ( ) )
used_height = max ( used_height , computed_min_height . resolved ( box , height_of_containing_block_as_length ) . resolved ( box ) . to_px ( box ) ) ;
2022-10-11 19:04:09 +02:00
// NOTE: The following is not directly part of any spec, but this is where we resolve
// the final used values for vertical margin/border/padding.
2022-07-26 01:30:50 +02:00
2022-10-11 19:04:09 +02:00
auto & box_state = m_state . get_mutable ( box ) ;
box_state . margin_top = margin_top . resolved ( box , width_of_containing_block_as_length ) . to_px ( box ) ;
box_state . margin_bottom = margin_bottom . resolved ( box , width_of_containing_block_as_length ) . to_px ( box ) ;
box_state . border_top = box . computed_values ( ) . border_top ( ) . width ;
box_state . border_bottom = box . computed_values ( ) . border_bottom ( ) . width ;
box_state . padding_top = box . computed_values ( ) . padding ( ) . top ( ) . resolved ( box , width_of_containing_block_as_length ) . to_px ( box ) ;
box_state . padding_bottom = box . computed_values ( ) . padding ( ) . bottom ( ) . resolved ( box , width_of_containing_block_as_length ) . to_px ( box ) ;
// And here is where we assign the box's content height.
2022-10-11 22:31:51 +02:00
box_state . set_content_height ( used_height ) ;
2021-01-06 18:18:46 +01:00
}
2022-10-01 13:48:28 +02:00
// NOTE: This is different from content_box_rect_in_ancestor_coordinate_space() as this does *not* follow the containing block chain up, but rather the parent() chain.
2022-11-23 17:46:10 +00:00
static CSSPixelRect content_box_rect_in_static_position_ancestor_coordinate_space ( Box const & box , Box const & ancestor_box , LayoutState const & state )
2022-10-01 13:48:28 +02:00
{
2022-11-04 20:32:50 +00:00
auto rect = content_box_rect ( box , state ) ;
2022-10-01 13:48:28 +02:00
if ( & box = = & ancestor_box )
return rect ;
for ( auto const * current = box . parent ( ) ; current ; current = current - > parent ( ) ) {
if ( current = = & ancestor_box )
return rect ;
auto const & current_state = state . get ( static_cast < Box const & > ( * current ) ) ;
2022-11-04 20:32:50 +00:00
rect . translate_by ( current_state . offset ) ;
2022-10-01 13:48:28 +02:00
}
// If we get here, ancestor_box was not an ancestor of `box`!
VERIFY_NOT_REACHED ( ) ;
}
// https://www.w3.org/TR/css-position-3/#staticpos-rect
2022-11-23 17:46:10 +00:00
CSSPixelPoint FormattingContext : : calculate_static_position ( Box const & box ) const
2022-10-01 13:48:28 +02:00
{
// NOTE: This is very ad-hoc.
// The purpose of this function is to calculate the approximate position that `box`
// would have had if it were position:static.
2022-11-23 17:46:10 +00:00
CSSPixels x = 0.0f ;
CSSPixels y = 0.0f ;
2022-10-01 13:48:28 +02:00
VERIFY ( box . parent ( ) ) ;
if ( box . parent ( ) - > children_are_inline ( ) ) {
// We're an abspos box with inline siblings. This is gonna get messy!
if ( auto * sibling = box . previous_sibling ( ) ) {
// Hard case: there's a previous sibling. This means there's already inline content
// preceding the hypothetical static position of `box` within its containing block.
// If we had been position:static, that inline content would have been wrapped in
// anonymous block box, so now we get to imagine what the world might have looked like
// in that scenario..
// Basically, we find its last associated line box fragment and place `box` under it.
// FIXME: I'm 100% sure this can be smarter, better and faster.
LineBoxFragment const * last_fragment = nullptr ;
auto & cb_state = m_state . get ( * sibling - > containing_block ( ) ) ;
for ( auto & line_box : cb_state . line_boxes ) {
for ( auto & fragment : line_box . fragments ( ) ) {
if ( & fragment . layout_node ( ) = = sibling )
last_fragment = & fragment ;
}
}
if ( last_fragment ) {
2022-10-31 19:46:55 +00:00
y = ( last_fragment - > offset ( ) . y ( ) + last_fragment - > height ( ) ) . value ( ) ;
2022-10-01 13:48:28 +02:00
}
} else {
// Easy case: no previous sibling, we're at the top of the containing block.
}
} else {
2022-11-19 05:11:38 +03:00
x = m_state . get ( box ) . margin_box_left ( ) ;
2022-10-01 13:48:28 +02:00
// We're among block siblings, Y can be calculated easily.
2022-12-25 17:13:21 +03:00
y = m_state . get ( box ) . margin_box_top ( ) ;
2022-10-01 13:48:28 +02:00
}
auto offset_to_static_parent = content_box_rect_in_static_position_ancestor_coordinate_space ( box , * box . containing_block ( ) , m_state ) ;
return offset_to_static_parent . location ( ) . translated ( x , y ) ;
}
2022-09-27 15:29:17 +02:00
void FormattingContext : : layout_absolutely_positioned_element ( Box const & box , AvailableSpace const & available_space )
2021-01-06 18:18:46 +01:00
{
2022-07-17 19:38:06 +02:00
auto & containing_block_state = m_state . get_mutable ( * box . containing_block ( ) ) ;
2022-09-27 15:29:17 +02:00
auto & box_state = m_state . get_mutable ( box ) ;
2022-07-17 19:38:06 +02:00
2022-09-27 15:29:17 +02:00
auto width_of_containing_block = available_space . width . to_px ( ) ;
auto height_of_containing_block = available_space . height . to_px ( ) ;
2022-07-09 15:17:47 +02:00
auto width_of_containing_block_as_length = CSS : : Length : : make_px ( width_of_containing_block ) ;
auto height_of_containing_block_as_length = CSS : : Length : : make_px ( height_of_containing_block ) ;
2021-01-06 18:18:46 +01:00
2022-07-09 15:17:47 +02:00
auto specified_width = box . computed_values ( ) . width ( ) . resolved ( box , width_of_containing_block_as_length ) . resolved ( box ) ;
2021-01-06 18:18:46 +01:00
2022-09-27 15:29:17 +02:00
compute_width_for_absolutely_positioned_element ( box , available_space ) ;
2022-11-09 15:11:21 +01:00
// NOTE: We compute height before *and* after doing inside layout.
// This is done so that inside layout can resolve percentage heights.
2022-11-09 16:16:10 +01:00
// In some situations, e.g with non-auto top & bottom values, the height can be determined early.
2023-01-06 21:05:18 +01:00
compute_height_for_absolutely_positioned_element ( box , available_space , BeforeOrAfterInsideLayout : : Before ) ;
2022-11-09 15:11:21 +01:00
2022-09-27 15:29:17 +02:00
auto independent_formatting_context = layout_inside ( box , LayoutMode : : Normal , box_state . available_inner_space_or_constraints_from ( available_space ) ) ;
2023-01-06 21:05:18 +01:00
compute_height_for_absolutely_positioned_element ( box , available_space , BeforeOrAfterInsideLayout : : After ) ;
2021-01-06 18:18:46 +01:00
2022-09-13 17:42:39 +02:00
box_state . margin_left = box . computed_values ( ) . margin ( ) . left ( ) . resolved ( box , width_of_containing_block_as_length ) . to_px ( box ) ;
2022-10-01 00:46:04 +02:00
box_state . margin_top = box . computed_values ( ) . margin ( ) . top ( ) . resolved ( box , width_of_containing_block_as_length ) . to_px ( box ) ;
2022-09-13 17:42:39 +02:00
box_state . margin_right = box . computed_values ( ) . margin ( ) . right ( ) . resolved ( box , width_of_containing_block_as_length ) . to_px ( box ) ;
2022-10-01 00:46:04 +02:00
box_state . margin_bottom = box . computed_values ( ) . margin ( ) . bottom ( ) . resolved ( box , width_of_containing_block_as_length ) . to_px ( box ) ;
2021-01-06 18:18:46 +01:00
2022-02-20 15:51:24 +01:00
box_state . border_left = box . computed_values ( ) . border_left ( ) . width ;
box_state . border_right = box . computed_values ( ) . border_right ( ) . width ;
box_state . border_top = box . computed_values ( ) . border_top ( ) . width ;
box_state . border_bottom = box . computed_values ( ) . border_bottom ( ) . width ;
2021-01-06 18:18:46 +01:00
2022-10-01 12:33:30 +02:00
auto const & computed_left = box . computed_values ( ) . inset ( ) . left ( ) ;
auto const & computed_right = box . computed_values ( ) . inset ( ) . right ( ) ;
auto const & computed_top = box . computed_values ( ) . inset ( ) . top ( ) ;
auto const & computed_bottom = box . computed_values ( ) . inset ( ) . bottom ( ) ;
2022-01-19 16:19:43 +00:00
2022-10-01 12:33:30 +02:00
box_state . inset_left = computed_left . resolved ( box , width_of_containing_block_as_length ) . to_px ( box ) ;
box_state . inset_top = computed_top . resolved ( box , height_of_containing_block_as_length ) . to_px ( box ) ;
box_state . inset_right = computed_right . resolved ( box , width_of_containing_block_as_length ) . to_px ( box ) ;
box_state . inset_bottom = computed_bottom . resolved ( box , height_of_containing_block_as_length ) . to_px ( box ) ;
if ( computed_left . is_auto ( ) & & box . computed_values ( ) . width ( ) . is_auto ( ) & & computed_right . is_auto ( ) ) {
2022-09-13 17:42:39 +02:00
if ( box . computed_values ( ) . margin ( ) . left ( ) . is_auto ( ) )
2022-02-20 15:51:24 +01:00
box_state . margin_left = 0 ;
2022-09-13 17:42:39 +02:00
if ( box . computed_values ( ) . margin ( ) . right ( ) . is_auto ( ) )
2022-02-20 15:51:24 +01:00
box_state . margin_right = 0 ;
2021-01-06 18:18:46 +01:00
}
2022-10-01 13:48:28 +02:00
auto static_position = calculate_static_position ( box ) ;
2021-01-06 18:18:46 +01:00
2022-11-23 17:46:10 +00:00
CSSPixelPoint used_offset ;
2022-08-10 19:21:29 +02:00
2022-10-01 12:33:30 +02:00
if ( ! computed_left . is_auto ( ) ) {
2022-11-23 17:46:10 +00:00
CSSPixels x_offset = box_state . inset_left
2022-02-20 15:51:24 +01:00
+ box_state . border_box_left ( ) ;
used_offset . set_x ( x_offset + box_state . margin_left ) ;
2022-10-01 12:33:30 +02:00
} else if ( ! computed_right . is_auto ( ) ) {
2022-11-04 20:32:50 +00:00
CSSPixels x_offset = CSSPixels ( 0 )
2022-03-26 13:26:42 +01:00
- box_state . inset_right
2022-02-20 15:51:24 +01:00
- box_state . border_box_right ( ) ;
2022-07-17 17:59:02 +02:00
used_offset . set_x ( width_of_containing_block + x_offset - box_state . content_width ( ) - box_state . margin_right ) ;
2021-01-06 18:18:46 +01:00
} else {
2022-11-19 05:11:38 +03:00
// NOTE: static position is content box position so border_box and margin should not be added
used_offset . set_x ( static_position . x ( ) ) ;
2021-01-06 18:18:46 +01:00
}
2022-10-01 12:33:30 +02:00
if ( ! computed_top . is_auto ( ) ) {
2022-11-23 17:46:10 +00:00
CSSPixels y_offset = box_state . inset_top
2022-02-20 15:51:24 +01:00
+ box_state . border_box_top ( ) ;
used_offset . set_y ( y_offset + box_state . margin_top ) ;
2022-10-01 12:33:30 +02:00
} else if ( ! computed_bottom . is_auto ( ) ) {
2022-11-04 20:32:50 +00:00
CSSPixels y_offset = CSSPixels ( 0 )
2022-03-26 13:26:42 +01:00
- box_state . inset_bottom
2022-02-20 15:51:24 +01:00
- box_state . border_box_bottom ( ) ;
2022-07-17 17:59:02 +02:00
used_offset . set_y ( height_of_containing_block + y_offset - box_state . content_height ( ) - box_state . margin_bottom ) ;
2021-01-06 18:18:46 +01:00
} else {
2022-11-19 05:11:38 +03:00
// NOTE: static position is content box position so border_box and margin should not be added
used_offset . set_y ( static_position . y ( ) ) ;
2021-01-06 18:18:46 +01:00
}
2022-09-14 14:38:55 +02:00
// NOTE: Absolutely positioned boxes are relative to the *padding edge* of the containing block.
used_offset . translate_by ( - containing_block_state . padding_left , - containing_block_state . padding_top ) ;
2022-11-04 20:32:50 +00:00
box_state . set_content_offset ( used_offset ) ;
2022-02-12 19:54:09 +01:00
if ( independent_formatting_context )
independent_formatting_context - > parent_context_did_dimension_child_root_box ( ) ;
2021-01-06 18:18:46 +01:00
}
2023-01-06 21:05:18 +01:00
void FormattingContext : : compute_height_for_absolutely_positioned_replaced_element ( ReplacedBox const & box , AvailableSpace const & available_space , BeforeOrAfterInsideLayout )
2021-01-06 18:18:46 +01:00
{
2021-05-04 10:14:58 -04:00
// 10.6.5 Absolutely positioned, replaced elements
// The used value of 'height' is determined as for inline replaced elements.
2022-11-04 20:32:50 +00:00
m_state . get_mutable ( box ) . set_content_height ( compute_height_for_replaced_element ( m_state , box , available_space ) ) ;
2022-02-20 15:51:24 +01:00
}
2022-03-27 16:07:19 +02:00
// https://www.w3.org/TR/css-position-3/#relpos-insets
2022-03-27 15:35:28 +02:00
void FormattingContext : : compute_inset ( Box const & box )
2022-02-20 15:51:24 +01:00
{
if ( box . computed_values ( ) . position ( ) ! = CSS : : Position : : Relative )
return ;
2022-11-04 20:32:50 +00:00
auto resolve_two_opposing_insets = [ & ] ( CSS : : LengthPercentage const & computed_start , CSS : : LengthPercentage const & computed_end , CSSPixels & used_start , CSSPixels & used_end , CSSPixels reference_for_percentage ) {
2022-03-27 16:07:19 +02:00
auto resolved_first = computed_start . resolved ( box , CSS : : Length : : make_px ( reference_for_percentage ) ) . resolved ( box ) ;
auto resolved_second = computed_end . resolved ( box , CSS : : Length : : make_px ( reference_for_percentage ) ) . resolved ( box ) ;
if ( resolved_first . is_auto ( ) & & resolved_second . is_auto ( ) ) {
// If opposing inset properties in an axis both compute to auto (their initial values),
// their used values are zero (i.e., the boxes stay in their original position in that axis).
used_start = 0 ;
used_end = 0 ;
} else if ( resolved_first . is_auto ( ) | | resolved_second . is_auto ( ) ) {
// If only one is auto, its used value becomes the negation of the other, and the box is shifted by the specified amount.
if ( resolved_first . is_auto ( ) ) {
used_end = resolved_second . to_px ( box ) ;
2022-11-04 20:32:50 +00:00
used_start = - used_end ;
2022-03-27 16:07:19 +02:00
} else {
used_start = resolved_first . to_px ( box ) ;
2022-11-04 20:32:50 +00:00
used_end = - used_start ;
2022-03-27 16:07:19 +02:00
}
} else {
// If neither is auto, the position is over-constrained; (with respect to the writing mode of its containing block)
// the computed end side value is ignored, and its used value becomes the negation of the start side.
used_start = resolved_first . to_px ( box ) ;
2022-11-04 20:32:50 +00:00
used_end = - used_start ;
2022-03-27 16:07:19 +02:00
}
} ;
2022-02-21 17:42:09 +01:00
auto & box_state = m_state . get_mutable ( box ) ;
2022-02-20 15:51:24 +01:00
auto const & computed_values = box . computed_values ( ) ;
2022-03-27 16:07:19 +02:00
// FIXME: Respect the containing block's writing-mode.
2022-09-13 17:42:39 +02:00
resolve_two_opposing_insets ( computed_values . inset ( ) . left ( ) , computed_values . inset ( ) . right ( ) , box_state . inset_left , box_state . inset_right , containing_block_width_for ( box ) ) ;
resolve_two_opposing_insets ( computed_values . inset ( ) . top ( ) , computed_values . inset ( ) . bottom ( ) , box_state . inset_top , box_state . inset_bottom , containing_block_height_for ( box ) ) ;
2021-01-06 18:18:46 +01:00
}
2022-10-14 15:09:40 +02:00
// https://drafts.csswg.org/css-sizing-3/#fit-content-size
2022-11-23 17:46:10 +00:00
CSSPixels FormattingContext : : calculate_fit_content_width ( Layout : : Box const & box , AvailableSpace const & available_space ) const
2022-03-12 14:36:59 +01:00
{
2022-10-14 15:09:40 +02:00
// If the available space in a given axis is definite,
// equal to clamp(min-content size, stretch-fit size, max-content size)
2022-03-12 14:36:59 +01:00
// (i.e. max(min-content size, min(max-content size, stretch-fit size))).
2022-10-14 15:09:40 +02:00
if ( available_space . width . is_definite ( ) ) {
return max ( calculate_min_content_width ( box ) ,
min ( calculate_stretch_fit_width ( box , available_space . width ) ,
calculate_max_content_width ( box ) ) ) ;
2022-03-12 14:36:59 +01:00
}
2022-07-20 17:59:46 +02:00
// When sizing under a min-content constraint, equal to the min-content size.
2022-10-03 23:37:38 +02:00
if ( available_space . width . is_min_content ( ) )
2022-07-20 17:59:46 +02:00
return calculate_min_content_width ( box ) ;
2022-10-14 15:09:40 +02:00
// Otherwise, equal to the max-content size in that axis.
return calculate_max_content_width ( box ) ;
2022-03-12 14:36:59 +01:00
}
2022-10-14 15:09:40 +02:00
// https://drafts.csswg.org/css-sizing-3/#fit-content-size
2022-11-23 17:46:10 +00:00
CSSPixels FormattingContext : : calculate_fit_content_height ( Layout : : Box const & box , AvailableSpace const & available_space ) const
2022-03-12 14:36:59 +01:00
{
2022-10-14 15:09:40 +02:00
// If the available space in a given axis is definite,
// equal to clamp(min-content size, stretch-fit size, max-content size)
// (i.e. max(min-content size, min(max-content size, stretch-fit size))).
if ( available_space . height . is_definite ( ) ) {
return max ( calculate_min_content_height ( box , available_space . width ) ,
min ( calculate_stretch_fit_height ( box , available_space . height ) ,
calculate_max_content_height ( box , available_space . width ) ) ) ;
}
2022-07-20 17:59:46 +02:00
// When sizing under a min-content constraint, equal to the min-content size.
2022-10-03 23:37:38 +02:00
if ( available_space . height . is_min_content ( ) )
return calculate_min_content_height ( box , available_space . width ) ;
2022-07-20 17:59:46 +02:00
2022-10-14 15:09:40 +02:00
// Otherwise, equal to the max-content size in that axis.
return calculate_max_content_height ( box , available_space . width ) ;
2022-03-12 14:36:59 +01:00
}
2022-03-30 19:44:18 +02:00
2022-11-23 17:46:10 +00:00
CSSPixels FormattingContext : : calculate_min_content_width ( Layout : : Box const & box ) const
2022-07-08 00:40:53 +02:00
{
2022-07-08 12:44:49 +02:00
if ( box . has_intrinsic_width ( ) )
return * box . intrinsic_width ( ) ;
auto & root_state = m_state . m_root ;
2022-07-16 23:30:32 +02:00
auto & cache = * root_state . intrinsic_sizes . ensure ( & box , [ ] { return adopt_own ( * new LayoutState : : IntrinsicSizes ) ; } ) ;
2022-07-08 12:44:49 +02:00
if ( cache . min_content_width . has_value ( ) )
return * cache . min_content_width ;
2022-07-16 23:30:32 +02:00
LayoutState throwaway_state ( & m_state ) ;
2022-07-12 02:17:17 +02:00
2022-07-09 15:17:47 +02:00
auto & box_state = throwaway_state . get_mutable ( box ) ;
box_state . width_constraint = SizeConstraint : : MinContent ;
2022-07-08 12:44:49 +02:00
auto context = const_cast < FormattingContext * > ( this ) - > create_independent_formatting_context_if_needed ( throwaway_state , box ) ;
VERIFY ( context ) ;
2022-10-03 20:52:28 +02:00
auto available_width = AvailableSize : : make_min_content ( ) ;
auto available_height = AvailableSize : : make_indefinite ( ) ;
context - > run ( box , LayoutMode : : IntrinsicSizing , AvailableSpace ( available_width , available_height ) ) ;
2022-07-08 12:44:49 +02:00
if ( context - > type ( ) = = FormattingContext : : Type : : Flex ) {
2022-07-17 17:59:02 +02:00
cache . min_content_width = box_state . content_width ( ) ;
2022-07-08 12:44:49 +02:00
} else {
2022-11-23 17:46:10 +00:00
cache . min_content_width = context - > greatest_child_width ( box ) . value ( ) ;
2022-07-08 12:44:49 +02:00
}
2022-07-11 23:53:52 +02:00
2022-11-04 20:32:50 +00:00
if ( ! isfinite ( cache . min_content_width - > value ( ) ) ) {
2022-07-11 23:53:52 +02:00
// HACK: If layout calculates a non-finite result, something went wrong. Force it to zero and log a little whine.
dbgln ( " FIXME: Calculated non-finite min-content width for {} " , box . debug_description ( ) ) ;
cache . min_content_width = 0 ;
}
2022-07-08 12:44:49 +02:00
return * cache . min_content_width ;
2022-07-08 00:40:53 +02:00
}
2022-11-23 17:46:10 +00:00
CSSPixels FormattingContext : : calculate_max_content_width ( Layout : : Box const & box ) const
2022-07-08 00:40:53 +02:00
{
2022-07-08 12:44:49 +02:00
if ( box . has_intrinsic_width ( ) )
return * box . intrinsic_width ( ) ;
auto & root_state = m_state . m_root ;
2022-07-16 23:30:32 +02:00
auto & cache = * root_state . intrinsic_sizes . ensure ( & box , [ ] { return adopt_own ( * new LayoutState : : IntrinsicSizes ) ; } ) ;
2022-07-08 12:44:49 +02:00
if ( cache . max_content_width . has_value ( ) )
return * cache . max_content_width ;
2022-07-16 23:30:32 +02:00
LayoutState throwaway_state ( & m_state ) ;
2022-07-12 02:17:17 +02:00
2022-07-09 15:17:47 +02:00
auto & box_state = throwaway_state . get_mutable ( box ) ;
box_state . width_constraint = SizeConstraint : : MaxContent ;
2022-07-08 12:44:49 +02:00
auto context = const_cast < FormattingContext * > ( this ) - > create_independent_formatting_context_if_needed ( throwaway_state , box ) ;
VERIFY ( context ) ;
2022-10-03 20:52:28 +02:00
auto available_width = AvailableSize : : make_max_content ( ) ;
auto available_height = AvailableSize : : make_indefinite ( ) ;
context - > run ( box , LayoutMode : : IntrinsicSizing , AvailableSpace ( available_width , available_height ) ) ;
2022-07-08 12:44:49 +02:00
if ( context - > type ( ) = = FormattingContext : : Type : : Flex ) {
2022-07-17 17:59:02 +02:00
cache . max_content_width = box_state . content_width ( ) ;
2022-07-08 12:44:49 +02:00
} else {
2022-11-23 17:46:10 +00:00
cache . max_content_width = context - > greatest_child_width ( box ) . value ( ) ;
2022-07-08 12:44:49 +02:00
}
2022-11-04 20:32:50 +00:00
if ( ! isfinite ( cache . max_content_width - > value ( ) ) ) {
2022-07-11 23:53:52 +02:00
// HACK: If layout calculates a non-finite result, something went wrong. Force it to zero and log a little whine.
dbgln ( " FIXME: Calculated non-finite max-content width for {} " , box . debug_description ( ) ) ;
cache . max_content_width = 0 ;
}
2022-07-08 12:44:49 +02:00
return * cache . max_content_width ;
2022-07-08 00:40:53 +02:00
}
2022-11-23 17:46:10 +00:00
CSSPixels FormattingContext : : calculate_min_content_height ( Layout : : Box const & box , AvailableSize const & available_width ) const
2022-07-08 00:40:53 +02:00
{
2022-07-08 12:44:49 +02:00
if ( box . has_intrinsic_height ( ) )
return * box . intrinsic_height ( ) ;
2022-10-08 20:27:54 +02:00
bool is_cacheable = available_width . is_definite ( ) | | available_width . is_intrinsic_sizing_constraint ( ) ;
2022-11-04 20:32:50 +00:00
Optional < CSSPixels > * cache_slot = nullptr ;
2022-10-08 20:27:54 +02:00
if ( is_cacheable ) {
auto & root_state = m_state . m_root ;
auto & cache = * root_state . intrinsic_sizes . ensure ( & box , [ ] { return adopt_own ( * new LayoutState : : IntrinsicSizes ) ; } ) ;
if ( available_width . is_definite ( ) ) {
2022-11-04 20:32:50 +00:00
cache_slot = & cache . min_content_height_with_definite_available_width . ensure ( available_width . to_px ( ) ) ;
2022-10-08 20:27:54 +02:00
} else if ( available_width . is_min_content ( ) ) {
cache_slot = & cache . min_content_height_with_min_content_available_width ;
} else if ( available_width . is_max_content ( ) ) {
cache_slot = & cache . min_content_height_with_max_content_available_width ;
}
}
2022-07-08 12:44:49 +02:00
2022-10-08 20:27:54 +02:00
if ( cache_slot & & cache_slot - > has_value ( ) )
return cache_slot - > value ( ) ;
2022-07-08 12:44:49 +02:00
2022-07-16 23:30:32 +02:00
LayoutState throwaway_state ( & m_state ) ;
2022-07-12 02:17:17 +02:00
2022-07-09 15:17:47 +02:00
auto & box_state = throwaway_state . get_mutable ( box ) ;
box_state . height_constraint = SizeConstraint : : MinContent ;
2022-07-08 12:44:49 +02:00
auto context = const_cast < FormattingContext * > ( this ) - > create_independent_formatting_context_if_needed ( throwaway_state , box ) ;
VERIFY ( context ) ;
2022-10-03 20:52:28 +02:00
2022-10-03 23:37:38 +02:00
context - > run ( box , LayoutMode : : IntrinsicSizing , AvailableSpace ( available_width , AvailableSize : : make_min_content ( ) ) ) ;
2022-10-03 20:52:28 +02:00
2022-10-08 20:27:54 +02:00
auto min_content_height = context - > automatic_content_height ( ) ;
2022-11-23 17:46:10 +00:00
if ( ! isfinite ( min_content_height . value ( ) ) ) {
2022-07-11 23:53:52 +02:00
// HACK: If layout calculates a non-finite result, something went wrong. Force it to zero and log a little whine.
dbgln ( " FIXME: Calculated non-finite min-content height for {} " , box . debug_description ( ) ) ;
2022-10-08 20:27:54 +02:00
min_content_height = 0 ;
2022-07-11 23:53:52 +02:00
}
2022-10-08 20:27:54 +02:00
if ( cache_slot ) {
2022-11-04 20:32:50 +00:00
* cache_slot = min_content_height ;
2022-10-08 20:27:54 +02:00
}
return min_content_height ;
2022-07-08 00:40:53 +02:00
}
2022-11-23 17:46:10 +00:00
CSSPixels FormattingContext : : calculate_max_content_height ( Layout : : Box const & box , AvailableSize const & available_width ) const
2022-07-08 00:40:53 +02:00
{
2022-07-08 12:44:49 +02:00
if ( box . has_intrinsic_height ( ) )
return * box . intrinsic_height ( ) ;
2022-10-08 20:27:54 +02:00
bool is_cacheable = available_width . is_definite ( ) | | available_width . is_intrinsic_sizing_constraint ( ) ;
2022-11-04 20:32:50 +00:00
Optional < CSSPixels > * cache_slot = nullptr ;
2022-10-08 20:27:54 +02:00
if ( is_cacheable ) {
auto & root_state = m_state . m_root ;
auto & cache = * root_state . intrinsic_sizes . ensure ( & box , [ ] { return adopt_own ( * new LayoutState : : IntrinsicSizes ) ; } ) ;
if ( available_width . is_definite ( ) ) {
2022-11-04 20:32:50 +00:00
cache_slot = & cache . max_content_height_with_definite_available_width . ensure ( available_width . to_px ( ) ) ;
2022-10-08 20:27:54 +02:00
} else if ( available_width . is_min_content ( ) ) {
cache_slot = & cache . max_content_height_with_min_content_available_width ;
} else if ( available_width . is_max_content ( ) ) {
cache_slot = & cache . max_content_height_with_max_content_available_width ;
}
}
2022-07-08 12:44:49 +02:00
2022-10-08 20:27:54 +02:00
if ( cache_slot & & cache_slot - > has_value ( ) )
return cache_slot - > value ( ) ;
2022-07-08 12:44:49 +02:00
2022-07-16 23:30:32 +02:00
LayoutState throwaway_state ( & m_state ) ;
2022-07-12 02:17:17 +02:00
2022-07-09 15:17:47 +02:00
auto & box_state = throwaway_state . get_mutable ( box ) ;
box_state . height_constraint = SizeConstraint : : MaxContent ;
2022-07-08 12:44:49 +02:00
auto context = const_cast < FormattingContext * > ( this ) - > create_independent_formatting_context_if_needed ( throwaway_state , box ) ;
VERIFY ( context ) ;
2022-10-03 20:52:28 +02:00
2022-10-03 23:37:38 +02:00
context - > run ( box , LayoutMode : : IntrinsicSizing , AvailableSpace ( available_width , AvailableSize : : make_max_content ( ) ) ) ;
2022-10-03 20:52:28 +02:00
2022-10-08 20:27:54 +02:00
auto max_content_height = context - > automatic_content_height ( ) ;
2022-07-11 23:53:52 +02:00
2022-11-23 17:46:10 +00:00
if ( ! isfinite ( max_content_height . value ( ) ) ) {
2022-07-11 23:53:52 +02:00
// HACK: If layout calculates a non-finite result, something went wrong. Force it to zero and log a little whine.
dbgln ( " FIXME: Calculated non-finite max-content height for {} " , box . debug_description ( ) ) ;
2022-10-08 20:27:54 +02:00
max_content_height = 0 ;
}
if ( cache_slot ) {
2022-11-04 20:32:50 +00:00
* cache_slot = max_content_height ;
2022-07-11 23:53:52 +02:00
}
2022-07-08 12:44:49 +02:00
2022-10-08 20:27:54 +02:00
return max_content_height ;
2022-07-08 00:40:53 +02:00
}
2022-11-21 22:17:24 +03:00
CSS : : Length FormattingContext : : calculate_inner_width ( Layout : : Box const & box , AvailableSize const & available_width , CSS : : Size const & width ) const
{
2022-11-23 17:46:10 +00:00
auto width_of_containing_block = available_width . to_px ( ) ;
2022-11-21 22:17:24 +03:00
auto width_of_containing_block_as_length_for_resolve = CSS : : Length : : make_px ( width_of_containing_block ) ;
if ( width . is_auto ( ) ) {
return width . resolved ( box , width_of_containing_block_as_length_for_resolve ) . resolved ( box ) ;
}
if ( ! available_width . is_definite ( ) )
width_of_containing_block_as_length_for_resolve = CSS : : Length : : make_px ( 0 ) ;
auto & computed_values = box . computed_values ( ) ;
if ( computed_values . box_sizing ( ) = = CSS : : BoxSizing : : BorderBox ) {
auto const padding_left = computed_values . padding ( ) . left ( ) . resolved ( box , width_of_containing_block_as_length_for_resolve ) . resolved ( box ) ;
auto const padding_right = computed_values . padding ( ) . right ( ) . resolved ( box , width_of_containing_block_as_length_for_resolve ) . resolved ( box ) ;
2022-11-23 17:46:10 +00:00
auto inner_width = width . resolved ( box , width_of_containing_block_as_length_for_resolve ) . resolved ( box ) . to_px ( box )
2022-11-21 22:17:24 +03:00
- computed_values . border_left ( ) . width
- padding_left . to_px ( box )
- computed_values . border_right ( ) . width
- padding_right . to_px ( box ) ;
return CSS : : Length : : make_px ( max ( inner_width , 0 ) ) ;
}
return width . resolved ( box , width_of_containing_block_as_length_for_resolve ) . resolved ( box ) ;
}
CSS : : Length FormattingContext : : calculate_inner_height ( Layout : : Box const & box , AvailableSize const & available_height , CSS : : Size const & height ) const
{
2022-11-23 17:46:10 +00:00
auto height_of_containing_block = available_height . to_px ( ) ;
2022-11-21 22:17:24 +03:00
auto height_of_containing_block_as_length_for_resolve = CSS : : Length : : make_px ( height_of_containing_block ) ;
if ( height . is_auto ( ) ) {
return height . resolved ( box , height_of_containing_block_as_length_for_resolve ) . resolved ( box ) ;
}
if ( ! available_height . is_definite ( ) )
height_of_containing_block_as_length_for_resolve = CSS : : Length : : make_px ( 0 ) ;
auto & computed_values = box . computed_values ( ) ;
if ( computed_values . box_sizing ( ) = = CSS : : BoxSizing : : BorderBox ) {
2022-11-25 19:02:50 +03:00
auto const padding_top = computed_values . padding ( ) . top ( ) . resolved ( box , height_of_containing_block_as_length_for_resolve ) . resolved ( box ) ;
auto const padding_bottom = computed_values . padding ( ) . bottom ( ) . resolved ( box , height_of_containing_block_as_length_for_resolve ) . resolved ( box ) ;
2022-11-21 22:17:24 +03:00
2022-11-23 17:46:10 +00:00
auto inner_height = height . resolved ( box , height_of_containing_block_as_length_for_resolve ) . resolved ( box ) . to_px ( box )
2022-11-21 22:17:24 +03:00
- computed_values . border_top ( ) . width
- padding_top . to_px ( box )
- computed_values . border_bottom ( ) . width
- padding_bottom . to_px ( box ) ;
return CSS : : Length : : make_px ( max ( inner_height , 0 ) ) ;
}
return height . resolved ( box , height_of_containing_block_as_length_for_resolve ) . resolved ( box ) ;
}
2022-11-23 17:46:10 +00:00
CSSPixels FormattingContext : : containing_block_width_for ( Box const & box , LayoutState const & state )
2022-07-09 15:17:47 +02:00
{
auto & containing_block_state = state . get ( * box . containing_block ( ) ) ;
auto & box_state = state . get ( box ) ;
switch ( box_state . width_constraint ) {
case SizeConstraint : : MinContent :
return 0 ;
case SizeConstraint : : MaxContent :
return INFINITY ;
case SizeConstraint : : None :
2022-07-17 17:59:02 +02:00
return containing_block_state . content_width ( ) ;
2022-07-09 15:17:47 +02:00
}
VERIFY_NOT_REACHED ( ) ;
}
2022-11-23 17:46:10 +00:00
CSSPixels FormattingContext : : containing_block_height_for ( Box const & box , LayoutState const & state )
2022-07-09 15:17:47 +02:00
{
auto & containing_block_state = state . get ( * box . containing_block ( ) ) ;
auto & box_state = state . get ( box ) ;
switch ( box_state . height_constraint ) {
case SizeConstraint : : MinContent :
return 0 ;
case SizeConstraint : : MaxContent :
return INFINITY ;
case SizeConstraint : : None :
2022-07-17 17:59:02 +02:00
return containing_block_state . content_height ( ) ;
2022-07-09 15:17:47 +02:00
}
VERIFY_NOT_REACHED ( ) ;
}
2022-09-26 15:41:22 +02:00
// https://drafts.csswg.org/css-sizing-3/#stretch-fit-size
2022-11-23 17:46:10 +00:00
CSSPixels FormattingContext : : calculate_stretch_fit_width ( Box const & box , AvailableSize const & available_width ) const
2022-09-26 15:41:22 +02:00
{
// The size a box would take if its outer size filled the available space in the given axis;
// in other words, the stretch fit into the available space, if that is definite.
// Undefined if the available space is indefinite.
auto const & box_state = m_state . get ( box ) ;
return available_width . to_px ( )
- box_state . margin_left
- box_state . margin_right
- box_state . padding_left
- box_state . padding_right
- box_state . border_left
- box_state . border_right ;
}
2022-10-14 15:09:40 +02:00
// https://drafts.csswg.org/css-sizing-3/#stretch-fit-size
2022-11-23 17:46:10 +00:00
CSSPixels FormattingContext : : calculate_stretch_fit_height ( Box const & box , AvailableSize const & available_height ) const
2022-10-14 15:09:40 +02:00
{
// The size a box would take if its outer size filled the available space in the given axis;
// in other words, the stretch fit into the available space, if that is definite.
// Undefined if the available space is indefinite.
auto const & box_state = m_state . get ( box ) ;
return available_height . to_px ( )
- box_state . margin_top
- box_state . margin_bottom
- box_state . padding_top
- box_state . padding_bottom
- box_state . border_top
- box_state . border_bottom ;
}
2022-11-03 18:44:40 +01:00
bool FormattingContext : : should_treat_width_as_auto ( Box const & box , AvailableSpace const & available_space )
{
return box . computed_values ( ) . width ( ) . is_auto ( )
| | ( box . computed_values ( ) . width ( ) . contains_percentage ( ) & & ! available_space . width . is_definite ( ) ) ;
}
bool FormattingContext : : should_treat_height_as_auto ( Box const & box , AvailableSpace const & available_space )
{
return box . computed_values ( ) . height ( ) . is_auto ( )
| | ( box . computed_values ( ) . height ( ) . contains_percentage ( ) & & ! available_space . height . is_definite ( ) ) ;
}
2022-12-28 10:11:54 +01:00
bool FormattingContext : : can_skip_is_anonymous_text_run ( Box & box )
{
if ( box . is_anonymous ( ) & & ! box . is_generated ( ) & & ! box . first_child_of_type < BlockContainer > ( ) ) {
bool contains_only_white_space = true ;
box . for_each_in_subtree ( [ & ] ( auto const & node ) {
if ( ! is < TextNode > ( node ) | | ! static_cast < TextNode const & > ( node ) . dom_node ( ) . data ( ) . is_whitespace ( ) ) {
contains_only_white_space = false ;
return IterationDecision : : Break ;
}
return IterationDecision : : Continue ;
} ) ;
if ( contains_only_white_space )
return true ;
}
return false ;
}
2020-11-22 13:38:18 +01:00
}