/* * Copyright (c) 2022-2023, Andreas Kling * * SPDX-License-Identifier: BSD-2-Clause */ #include #include #include #include #include #include #include #include #include namespace Web::Layout { LayoutState::LayoutState(LayoutState const* parent) : m_parent(parent) , m_root(find_root()) { } LayoutState::~LayoutState() { } LayoutState::UsedValues& LayoutState::get_mutable(NodeWithStyle const& node) { if (auto* used_values = used_values_per_layout_node.get(&node).value_or(nullptr)) return *used_values; for (auto const* ancestor = m_parent; ancestor; ancestor = ancestor->m_parent) { if (auto* ancestor_used_values = ancestor->used_values_per_layout_node.get(&node).value_or(nullptr)) { auto cow_used_values = adopt_own(*new UsedValues(*ancestor_used_values)); auto* cow_used_values_ptr = cow_used_values.ptr(); used_values_per_layout_node.set(&node, move(cow_used_values)); return *cow_used_values_ptr; } } auto const* containing_block_used_values = node.is_viewport() ? nullptr : &get(*node.containing_block()); auto new_used_values = adopt_own(*new UsedValues); auto* new_used_values_ptr = new_used_values.ptr(); new_used_values->set_node(const_cast(node), containing_block_used_values); used_values_per_layout_node.set(&node, move(new_used_values)); return *new_used_values_ptr; } LayoutState::UsedValues const& LayoutState::get(NodeWithStyle const& node) const { if (auto const* used_values = used_values_per_layout_node.get(&node).value_or(nullptr)) return *used_values; for (auto const* ancestor = m_parent; ancestor; ancestor = ancestor->m_parent) { if (auto const* ancestor_used_values = ancestor->used_values_per_layout_node.get(&node).value_or(nullptr)) return *ancestor_used_values; } auto const* containing_block_used_values = node.is_viewport() ? nullptr : &get(*node.containing_block()); auto new_used_values = adopt_own(*new UsedValues); auto* new_used_values_ptr = new_used_values.ptr(); new_used_values->set_node(const_cast(node), containing_block_used_values); const_cast(this)->used_values_per_layout_node.set(&node, move(new_used_values)); return *new_used_values_ptr; } // https://www.w3.org/TR/css-overflow-3/#scrollable-overflow static CSSPixelRect measure_scrollable_overflow(Box const& box) { if (!box.paintable_box()) return {}; auto& paintable_box = const_cast(*box.paintable_box()); if (paintable_box.scrollable_overflow_rect().has_value()) return paintable_box.scrollable_overflow_rect().value(); // The scrollable overflow area is the union of: // - The scroll container’s own padding box. auto scrollable_overflow_rect = paintable_box.absolute_padding_box_rect(); // - All line boxes directly contained by the scroll container. if (is(box.paintable())) { for (auto const& fragment : static_cast(*box.paintable()).fragments()) { scrollable_overflow_rect = scrollable_overflow_rect.united(fragment.absolute_rect()); } } // - The border boxes of all boxes for which it is the containing block // and whose border boxes are positioned not wholly in the negative scrollable overflow region, // FIXME: accounting for transforms by projecting each box onto the plane of the element that establishes its 3D rendering context. [CSS3-TRANSFORMS] if (!box.children_are_inline()) { box.for_each_child_of_type([&box, &scrollable_overflow_rect](Box const& child) { if (!child.paintable_box()) return IterationDecision::Continue; auto child_border_box = child.paintable_box()->absolute_border_box_rect(); // NOTE: Here we check that the child is not wholly in the negative scrollable overflow region. if (child_border_box.bottom() > 0 && child_border_box.right() > 0) scrollable_overflow_rect = scrollable_overflow_rect.united(child_border_box); // - The scrollable overflow areas of all of the above boxes // (including zero-area boxes and accounting for transforms as described above), // provided they themselves have overflow: visible (i.e. do not themselves trap the overflow) // and that scrollable overflow is not already clipped (e.g. by the clip property or the contain property). if (is(box) || child.computed_values().overflow_x() == CSS::Overflow::Visible || child.computed_values().overflow_y() == CSS::Overflow::Visible) { auto child_scrollable_overflow = measure_scrollable_overflow(child); if (is(box) || child.computed_values().overflow_x() == CSS::Overflow::Visible) scrollable_overflow_rect.unite_horizontally(child_scrollable_overflow); if (is(box) || child.computed_values().overflow_y() == CSS::Overflow::Visible) scrollable_overflow_rect.unite_vertically(child_scrollable_overflow); } return IterationDecision::Continue; }); } else { box.for_each_child([&scrollable_overflow_rect](Node const& child) { if (child.paintable() && child.paintable()->is_inline_paintable()) { for (auto const& fragment : static_cast(*child.paintable()).fragments()) scrollable_overflow_rect = scrollable_overflow_rect.united(fragment.absolute_rect()); } }); } // FIXME: - The margin areas of grid item and flex item boxes for which the box establishes a containing block. // FIXME: - Additional padding added to the end-side of the scrollable overflow rectangle as necessary // to enable a scroll position that satisfies the requirements of place-content: end alignment. paintable_box.set_overflow_data(Painting::PaintableBox::OverflowData { .scrollable_overflow_rect = scrollable_overflow_rect, .has_scrollable_overflow = !paintable_box.absolute_padding_box_rect().contains(scrollable_overflow_rect), }); return scrollable_overflow_rect; } void LayoutState::resolve_relative_positions() { // This function resolves relative position offsets of fragments that belong to inline paintables. // It runs *after* the paint tree has been constructed, so it modifies paintable node & fragment offsets directly. for (auto& it : used_values_per_layout_node) { auto& used_values = *it.value; auto& node = const_cast(used_values.node()); auto* paintable = node.paintable(); if (!paintable) continue; if (!is(*paintable)) continue; auto const& inline_paintable = static_cast(*paintable); for (auto& fragment : inline_paintable.fragments()) { auto const& fragment_node = fragment.layout_node(); if (!is(*fragment_node.parent())) continue; // Collect effective relative position offset from inline-flow parent chain. CSSPixelPoint offset; for (auto* ancestor = fragment_node.parent(); ancestor; ancestor = ancestor->parent()) { if (!is(*ancestor)) break; if (!ancestor->display().is_inline_outside() || !ancestor->display().is_flow_inside()) break; if (ancestor->computed_values().position() == CSS::Positioning::Relative) { auto const& ancestor_node = static_cast(*ancestor); auto const& inset = ancestor_node.box_model().inset; offset.translate_by(inset.left, inset.top); } } const_cast(fragment).set_offset(fragment.offset().translated(offset)); } } } static void build_paint_tree(Node& node, Painting::Paintable* parent_paintable = nullptr) { Painting::Paintable* paintable = nullptr; if (node.paintable()) { paintable = const_cast(node.paintable()); if (parent_paintable && !paintable->forms_unconnected_subtree()) { VERIFY(!paintable->parent()); parent_paintable->append_child(*paintable); } paintable->set_dom_node(node.dom_node()); if (node.dom_node()) node.dom_node()->set_paintable(paintable); } for (auto* child = node.first_child(); child; child = child->next_sibling()) { build_paint_tree(*child, paintable); } } static Painting::BorderRadiiData normalized_border_radii_data(Layout::Node const& node, CSSPixelRect const& rect, CSS::BorderRadiusData top_left_radius, CSS::BorderRadiusData top_right_radius, CSS::BorderRadiusData bottom_right_radius, CSS::BorderRadiusData bottom_left_radius) { Painting::BorderRadiusData bottom_left_radius_px {}; Painting::BorderRadiusData bottom_right_radius_px {}; Painting::BorderRadiusData top_left_radius_px {}; Painting::BorderRadiusData top_right_radius_px {}; bottom_left_radius_px.horizontal_radius = bottom_left_radius.horizontal_radius.to_px(node, rect.width()); bottom_right_radius_px.horizontal_radius = bottom_right_radius.horizontal_radius.to_px(node, rect.width()); top_left_radius_px.horizontal_radius = top_left_radius.horizontal_radius.to_px(node, rect.width()); top_right_radius_px.horizontal_radius = top_right_radius.horizontal_radius.to_px(node, rect.width()); bottom_left_radius_px.vertical_radius = bottom_left_radius.vertical_radius.to_px(node, rect.height()); bottom_right_radius_px.vertical_radius = bottom_right_radius.vertical_radius.to_px(node, rect.height()); top_left_radius_px.vertical_radius = top_left_radius.vertical_radius.to_px(node, rect.height()); top_right_radius_px.vertical_radius = top_right_radius.vertical_radius.to_px(node, rect.height()); // Scale overlapping curves according to https://www.w3.org/TR/css-backgrounds-3/#corner-overlap // Let f = min(Li/Si), where i ∈ {top, right, bottom, left}, // Si is the sum of the two corresponding radii of the corners on side i, // and Ltop = Lbottom = the width of the box, and Lleft = Lright = the height of the box. auto l_top = rect.width(); auto l_bottom = l_top; auto l_left = rect.height(); auto l_right = l_left; auto s_top = (top_left_radius_px.horizontal_radius + top_right_radius_px.horizontal_radius); auto s_right = (top_right_radius_px.vertical_radius + bottom_right_radius_px.vertical_radius); auto s_bottom = (bottom_left_radius_px.horizontal_radius + bottom_right_radius_px.horizontal_radius); auto s_left = (top_left_radius_px.vertical_radius + bottom_left_radius_px.vertical_radius); CSSPixelFraction f = 1; f = (s_top != 0) ? min(f, l_top / s_top) : f; f = (s_right != 0) ? min(f, l_right / s_right) : f; f = (s_bottom != 0) ? min(f, l_bottom / s_bottom) : f; f = (s_left != 0) ? min(f, l_left / s_left) : f; // If f < 1, then all corner radii are reduced by multiplying them by f. if (f < 1) { top_left_radius_px.horizontal_radius *= f; top_left_radius_px.vertical_radius *= f; top_right_radius_px.horizontal_radius *= f; top_right_radius_px.vertical_radius *= f; bottom_right_radius_px.horizontal_radius *= f; bottom_right_radius_px.vertical_radius *= f; bottom_left_radius_px.horizontal_radius *= f; bottom_left_radius_px.vertical_radius *= f; } return Painting::BorderRadiiData { top_left_radius_px, top_right_radius_px, bottom_right_radius_px, bottom_left_radius_px }; } void LayoutState::resolve_layout_dependent_properties() { // Resolves layout-dependent properties not handled during layout and stores them in the paint tree. // Properties resolved include: // - Border radii // - Box shadows // - Text shadows // - Transforms // - Transform origins for (auto& it : used_values_per_layout_node) { auto& used_values = *it.value; auto& node = const_cast(used_values.node()); auto* paintable = node.paintable(); auto const is_inline_paintable = paintable && paintable->is_inline_paintable(); auto const is_paintable_box = paintable && paintable->is_paintable_box(); auto const is_paintable_with_lines = paintable && paintable->is_paintable_with_lines(); // Border radii if (is_inline_paintable) { auto& inline_paintable = static_cast(*paintable); auto& fragments = inline_paintable.fragments(); auto const& top_left_border_radius = inline_paintable.computed_values().border_top_left_radius(); auto const& top_right_border_radius = inline_paintable.computed_values().border_top_right_radius(); auto const& bottom_right_border_radius = inline_paintable.computed_values().border_bottom_right_radius(); auto const& bottom_left_border_radius = inline_paintable.computed_values().border_bottom_left_radius(); auto containing_block_position_in_absolute_coordinates = inline_paintable.containing_block()->paintable_box()->absolute_position(); for (size_t i = 0; i < fragments.size(); ++i) { auto is_first_fragment = i == 0; auto is_last_fragment = i == fragments.size() - 1; auto& fragment = fragments[i]; CSSPixelRect absolute_fragment_rect { containing_block_position_in_absolute_coordinates.translated(fragment.offset()), fragment.size() }; if (is_first_fragment) { auto extra_start_width = inline_paintable.box_model().padding.left; absolute_fragment_rect.translate_by(-extra_start_width, 0); absolute_fragment_rect.set_width(absolute_fragment_rect.width() + extra_start_width); } if (is_last_fragment) { auto extra_end_width = inline_paintable.box_model().padding.right; absolute_fragment_rect.set_width(absolute_fragment_rect.width() + extra_end_width); } auto border_radii_data = normalized_border_radii_data(inline_paintable.layout_node(), absolute_fragment_rect, top_left_border_radius, top_right_border_radius, bottom_right_border_radius, bottom_left_border_radius); fragment.set_border_radii_data(border_radii_data); } } // Border radii if (is_paintable_box) { auto& paintable_box = static_cast(*paintable); CSSPixelRect const border_rect { 0, 0, used_values.border_box_width(), used_values.border_box_height() }; auto const& border_top_left_radius = node.computed_values().border_top_left_radius(); auto const& border_top_right_radius = node.computed_values().border_top_right_radius(); auto const& border_bottom_right_radius = node.computed_values().border_bottom_right_radius(); auto const& border_bottom_left_radius = node.computed_values().border_bottom_left_radius(); auto radii_data = normalized_border_radii_data(node, border_rect, border_top_left_radius, border_top_right_radius, border_bottom_right_radius, border_bottom_left_radius); paintable_box.set_border_radii_data(radii_data); } // Box shadows auto const& box_shadow_data = node.computed_values().box_shadow(); if (!box_shadow_data.is_empty()) { Vector resolved_box_shadow_data; resolved_box_shadow_data.ensure_capacity(box_shadow_data.size()); for (auto const& layer : box_shadow_data) { resolved_box_shadow_data.empend( layer.color, layer.offset_x.to_px(node), layer.offset_y.to_px(node), layer.blur_radius.to_px(node), layer.spread_distance.to_px(node), layer.placement == CSS::ShadowPlacement::Outer ? Painting::ShadowPlacement::Outer : Painting::ShadowPlacement::Inner); } if (paintable && is(*paintable)) { auto& paintable_box = static_cast(*paintable); paintable_box.set_box_shadow_data(move(resolved_box_shadow_data)); } else if (paintable && is(*paintable)) { auto& inline_paintable = static_cast(*paintable); inline_paintable.set_box_shadow_data(move(resolved_box_shadow_data)); } else { VERIFY_NOT_REACHED(); } } // Text shadows if (is_paintable_with_lines) { auto const& paintable_with_lines = static_cast(*paintable); for (auto const& fragment : paintable_with_lines.fragments()) { auto const& text_shadow = fragment.m_layout_node->computed_values().text_shadow(); if (!text_shadow.is_empty()) { Vector resolved_shadow_data; resolved_shadow_data.ensure_capacity(text_shadow.size()); for (auto const& layer : text_shadow) { resolved_shadow_data.empend( layer.color, layer.offset_x.to_px(paintable_with_lines.layout_node()), layer.offset_y.to_px(paintable_with_lines.layout_node()), layer.blur_radius.to_px(paintable_with_lines.layout_node()), layer.spread_distance.to_px(paintable_with_lines.layout_node()), Painting::ShadowPlacement::Outer); } const_cast(fragment).set_shadows(move(resolved_shadow_data)); } } } // Transform and transform origin if (is_paintable_box) { auto& paintable_box = static_cast(*paintable); auto const& transformations = paintable_box.computed_values().transformations(); if (!transformations.is_empty()) { auto matrix = Gfx::FloatMatrix4x4::identity(); for (auto const& transform : transformations) matrix = matrix * transform.to_matrix(paintable_box).release_value(); paintable_box.set_transform(matrix); } auto const& transform_origin = paintable_box.computed_values().transform_origin(); // FIXME: respect transform-box property auto const& reference_box = paintable_box.absolute_border_box_rect(); auto x = reference_box.left() + transform_origin.x.to_px(node, reference_box.width()); auto y = reference_box.top() + transform_origin.y.to_px(node, reference_box.height()); paintable_box.set_transform_origin({ x, y }); paintable_box.set_transform_origin({ x, y }); } } } void LayoutState::commit(Box& root) { // Only the top-level LayoutState should ever be committed. VERIFY(!m_parent); // NOTE: In case this is a relayout of an existing tree, we start by detaching the old paint tree // from the layout tree. This is done to ensure that we don't end up with any old-tree pointers // when text paintables shift around in the tree. root.for_each_in_inclusive_subtree([&](Layout::Node& node) { node.set_paintable(nullptr); return IterationDecision::Continue; }); HashTable text_nodes; Vector paintables_with_lines; for (auto& it : used_values_per_layout_node) { auto& used_values = *it.value; auto& node = const_cast(used_values.node()); if (is(node)) { // Transfer box model metrics. auto& box_model = static_cast(node).box_model(); box_model.inset = { used_values.inset_top, used_values.inset_right, used_values.inset_bottom, used_values.inset_left }; box_model.padding = { used_values.padding_top, used_values.padding_right, used_values.padding_bottom, used_values.padding_left }; box_model.border = { used_values.border_top, used_values.border_right, used_values.border_bottom, used_values.border_left }; box_model.margin = { used_values.margin_top, used_values.margin_right, used_values.margin_bottom, used_values.margin_left }; } auto paintable = node.create_paintable(); node.set_paintable(paintable); // For boxes, transfer all the state needed for painting. if (paintable && is(*paintable)) { auto& paintable_box = static_cast(*paintable); paintable_box.set_offset(used_values.offset); paintable_box.set_content_size(used_values.content_width(), used_values.content_height()); if (used_values.override_borders_data().has_value()) { paintable_box.set_override_borders_data(used_values.override_borders_data().value()); } if (used_values.table_cell_coordinates().has_value()) { paintable_box.set_table_cell_coordinates(used_values.table_cell_coordinates().value()); } if (is(paintable_box)) { auto& paintable_with_lines = static_cast(paintable_box); for (auto& line_box : used_values.line_boxes) { for (auto& fragment : line_box.fragments()) paintable_with_lines.add_fragment(fragment); } paintables_with_lines.append(paintable_with_lines); } if (used_values.computed_svg_transforms().has_value() && is(paintable_box)) { auto& svg_graphics_paintable = static_cast(paintable_box); svg_graphics_paintable.set_computed_transforms(*used_values.computed_svg_transforms()); } if (used_values.computed_svg_path().has_value() && is(paintable_box)) { auto& svg_geometry_paintable = static_cast(paintable_box); svg_geometry_paintable.set_computed_path(move(*used_values.computed_svg_path())); } } } // Resolve relative positions for regular boxes (not line box fragments): // NOTE: This needs to occur before fragments are transferred into the corresponding inline paintables, because // after this transfer, the containing_line_box_fragment will no longer be valid. for (auto& it : used_values_per_layout_node) { auto& used_values = *it.value; auto& node = const_cast(used_values.node()); if (!node.is_box()) continue; auto& paintable = static_cast(*node.paintable()); CSSPixelPoint offset; if (used_values.containing_line_box_fragment.has_value()) { // Atomic inline case: // We know that `node` is an atomic inline because `containing_line_box_fragments` refers to the // line box fragment in the parent block container that contains it. auto const& containing_line_box_fragment = used_values.containing_line_box_fragment.value(); auto const& containing_block = *node.containing_block(); auto const& containing_block_used_values = get(containing_block); auto const& fragment = containing_block_used_values.line_boxes[containing_line_box_fragment.line_box_index].fragments()[containing_line_box_fragment.fragment_index]; // The fragment has the final offset for the atomic inline, so we just need to copy it from there. offset = fragment.offset(); } else { // Not an atomic inline, much simpler case. offset = used_values.offset; } // Apply relative position inset if appropriate. if (node.computed_values().position() == CSS::Positioning::Relative && is(node)) { auto const& inset = static_cast(node).box_model().inset; offset.translate_by(inset.left, inset.top); } paintable.set_offset(offset); } // Make a pass over all the line boxes to: // - Collect all text nodes, so we can create paintables for them later. // - Relocate fragments into matching inline paintables for (auto& paintable_with_lines : paintables_with_lines) { Vector fragments_with_inline_paintables_removed; for (auto& fragment : paintable_with_lines.fragments()) { if (fragment.layout_node().is_text_node()) text_nodes.set(static_cast(const_cast(&fragment.layout_node()))); auto find_closest_inline_paintable = [&](auto& fragment) -> Painting::InlinePaintable const* { for (auto const* parent = fragment.layout_node().parent(); parent; parent = parent->parent()) { if (is(*parent)) return static_cast(parent->paintable()); } return nullptr; }; if (auto const* inline_paintable = find_closest_inline_paintable(fragment)) { const_cast(inline_paintable)->fragments().append(fragment); } else { fragments_with_inline_paintables_removed.append(fragment); } } paintable_with_lines.set_fragments(move(fragments_with_inline_paintables_removed)); } for (auto* text_node : text_nodes) { text_node->set_paintable(text_node->create_paintable()); auto* paintable = text_node->paintable(); auto const& font = text_node->first_available_font(); auto const glyph_height = CSSPixels::nearest_value_for(font.pixel_size()); auto const css_line_thickness = [&] { auto computed_thickness = text_node->computed_values().text_decoration_thickness().resolved(*text_node, CSS::Length(1, CSS::Length::Type::Em).to_px(*text_node)); if (computed_thickness.is_auto()) return max(glyph_height.scaled(0.1), 1); return computed_thickness.to_px(*text_node); }(); auto& text_paintable = static_cast(*paintable); text_paintable.set_text_decoration_thickness(css_line_thickness); } build_paint_tree(root); resolve_relative_positions(); // Measure overflow in scroll containers. for (auto& it : used_values_per_layout_node) { auto& used_values = *it.value; if (!used_values.node().is_box()) continue; auto const& box = static_cast(used_values.node()); measure_scrollable_overflow(box); } resolve_layout_dependent_properties(); } void LayoutState::UsedValues::set_node(NodeWithStyle& node, UsedValues const* containing_block_used_values) { m_node = &node; m_containing_block_used_values = containing_block_used_values; // NOTE: In the code below, we decide if `node` has definite width and/or height. // This attempts to cover all the *general* cases where CSS considers sizes to be definite. // If `node` has definite values for min/max-width or min/max-height and a definite // preferred size in the same axis, we clamp the preferred size here as well. // // There are additional cases where CSS considers values to be definite. We model all of // those by having our engine consider sizes to be definite *once they are assigned to // the UsedValues by calling set_content_width() or set_content_height(). auto const& computed_values = node.computed_values(); auto adjust_for_box_sizing = [&](CSSPixels unadjusted_pixels, CSS::Size const& computed_size, bool width) -> CSSPixels { // box-sizing: content-box and/or automatic size don't require any adjustment. if (computed_values.box_sizing() == CSS::BoxSizing::ContentBox || computed_size.is_auto()) return unadjusted_pixels; // box-sizing: border-box requires us to subtract the relevant border and padding from the size. CSSPixels border_and_padding; if (width) { border_and_padding = computed_values.border_left().width + computed_values.padding().left().to_px(*m_node, containing_block_used_values->content_width()) + computed_values.border_right().width + computed_values.padding().right().to_px(*m_node, containing_block_used_values->content_width()); } else { border_and_padding = computed_values.border_top().width + computed_values.padding().top().to_px(*m_node, containing_block_used_values->content_width()) + computed_values.border_bottom().width + computed_values.padding().bottom().to_px(*m_node, containing_block_used_values->content_width()); } return unadjusted_pixels - border_and_padding; }; auto is_definite_size = [&](CSS::Size const& size, CSSPixels& resolved_definite_size, bool width) { // A size that can be determined without performing layout; that is, // a , // a measure of text (without consideration of line-wrapping), // a size of the initial containing block, // or a or other formula (such as the “stretch-fit” sizing of non-replaced blocks [CSS2]) that is resolved solely against definite sizes. auto containing_block_has_definite_size = containing_block_used_values ? (width ? containing_block_used_values->has_definite_width() : containing_block_used_values->has_definite_height()) : false; if (size.is_auto()) { // NOTE: The width of a non-flex-item block is considered definite if it's auto and the containing block has definite width. if (width && !node.is_floating() && !node.is_absolutely_positioned() && node.display().is_block_outside() && node.parent() && !node.parent()->is_floating() && (node.parent()->display().is_flow_root_inside() || node.parent()->display().is_flow_inside())) { if (containing_block_has_definite_size) { CSSPixels available_width = containing_block_used_values->content_width(); resolved_definite_size = available_width - margin_left - margin_right - padding_left - padding_right - border_left - border_right; return true; } return false; } return false; } if (size.is_calculated()) { if (size.calculated().contains_percentage()) { if (!containing_block_has_definite_size) return false; auto containing_block_size_as_length = width ? containing_block_used_values->content_width() : containing_block_used_values->content_height(); resolved_definite_size = adjust_for_box_sizing(size.calculated().resolve_length_percentage(node, containing_block_size_as_length).value_or(CSS::Length::make_auto()).to_px(node), size, width); return true; } resolved_definite_size = adjust_for_box_sizing(size.calculated().resolve_length(node)->to_px(node), size, width); return true; } if (size.is_length()) { VERIFY(!size.is_auto()); // This should have been covered by the Size::is_auto() branch above. resolved_definite_size = adjust_for_box_sizing(size.length().to_px(node), size, width); return true; } if (size.is_percentage()) { if (containing_block_has_definite_size) { auto containing_block_size = width ? containing_block_used_values->content_width() : containing_block_used_values->content_height(); resolved_definite_size = adjust_for_box_sizing(containing_block_size.scaled(size.percentage().as_fraction()), size, width); return true; } return false; } // FIXME: Determine if calc() value is definite. return false; }; CSSPixels min_width = 0; bool has_definite_min_width = is_definite_size(computed_values.min_width(), min_width, true); CSSPixels max_width = 0; bool has_definite_max_width = is_definite_size(computed_values.max_width(), max_width, true); CSSPixels min_height = 0; bool has_definite_min_height = is_definite_size(computed_values.min_height(), min_height, false); CSSPixels max_height = 0; bool has_definite_max_height = is_definite_size(computed_values.max_height(), max_height, false); m_has_definite_width = is_definite_size(computed_values.width(), m_content_width, true); m_has_definite_height = is_definite_size(computed_values.height(), m_content_height, false); if (m_has_definite_width) { if (has_definite_min_width) m_content_width = max(min_width, m_content_width); if (has_definite_max_width) m_content_width = min(max_width, m_content_width); } if (m_has_definite_height) { if (has_definite_min_height) m_content_height = max(min_height, m_content_height); if (has_definite_max_height) m_content_height = min(max_height, m_content_height); } } void LayoutState::UsedValues::set_content_width(CSSPixels width) { VERIFY(!width.might_be_saturated()); if (width < 0) { // Negative widths are not allowed in CSS. We have a bug somewhere! Clamp to 0 to avoid doing too much damage. dbgln_if(LIBWEB_CSS_DEBUG, "FIXME: Layout calculated a negative width for {}: {}", m_node->debug_description(), width); width = 0; } m_content_width = width; m_has_definite_width = true; } void LayoutState::UsedValues::set_content_height(CSSPixels height) { VERIFY(!height.might_be_saturated()); if (height < 0) { // Negative heights are not allowed in CSS. We have a bug somewhere! Clamp to 0 to avoid doing too much damage. dbgln_if(LIBWEB_CSS_DEBUG, "FIXME: Layout calculated a negative height for {}: {}", m_node->debug_description(), height); height = 0; } m_content_height = height; m_has_definite_height = true; } void LayoutState::UsedValues::set_temporary_content_width(CSSPixels width) { m_content_width = width; } void LayoutState::UsedValues::set_temporary_content_height(CSSPixels height) { m_content_height = height; } AvailableSize LayoutState::UsedValues::available_width_inside() const { if (width_constraint == SizeConstraint::MinContent) return AvailableSize::make_min_content(); if (width_constraint == SizeConstraint::MaxContent) return AvailableSize::make_max_content(); if (has_definite_width()) return AvailableSize::make_definite(m_content_width); return AvailableSize::make_indefinite(); } AvailableSize LayoutState::UsedValues::available_height_inside() const { if (height_constraint == SizeConstraint::MinContent) return AvailableSize::make_min_content(); if (height_constraint == SizeConstraint::MaxContent) return AvailableSize::make_max_content(); if (has_definite_height()) return AvailableSize::make_definite(m_content_height); return AvailableSize::make_indefinite(); } AvailableSpace LayoutState::UsedValues::available_inner_space_or_constraints_from(AvailableSpace const& outer_space) const { auto inner_width = available_width_inside(); auto inner_height = available_height_inside(); if (inner_width.is_indefinite() && outer_space.width.is_intrinsic_sizing_constraint()) inner_width = outer_space.width; if (inner_height.is_indefinite() && outer_space.height.is_intrinsic_sizing_constraint()) inner_height = outer_space.height; return AvailableSpace(inner_width, inner_height); } void LayoutState::UsedValues::set_content_offset(CSSPixelPoint new_offset) { set_content_x(new_offset.x()); set_content_y(new_offset.y()); } void LayoutState::UsedValues::set_content_x(CSSPixels x) { offset.set_x(x); } void LayoutState::UsedValues::set_content_y(CSSPixels y) { offset.set_y(y); } void LayoutState::UsedValues::set_indefinite_content_width() { m_has_definite_width = false; } void LayoutState::UsedValues::set_indefinite_content_height() { m_has_definite_height = false; } }