Serenity Operating System
at master 292 lines 11 kB view raw
1/* 2 * Copyright (c) 2022, Andreas Kling <kling@serenityos.org> 3 * 4 * SPDX-License-Identifier: BSD-2-Clause 5 */ 6 7#include <LibWeb/Layout/BreakNode.h> 8#include <LibWeb/Layout/InlineFormattingContext.h> 9#include <LibWeb/Layout/InlineLevelIterator.h> 10#include <LibWeb/Layout/InlineNode.h> 11#include <LibWeb/Layout/ListItemMarkerBox.h> 12#include <LibWeb/Layout/ReplacedBox.h> 13 14namespace Web::Layout { 15 16InlineLevelIterator::InlineLevelIterator(Layout::InlineFormattingContext& inline_formatting_context, Layout::LayoutState& layout_state, Layout::BlockContainer const& container, LayoutMode layout_mode) 17 : m_inline_formatting_context(inline_formatting_context) 18 , m_layout_state(layout_state) 19 , m_container(container) 20 , m_container_state(layout_state.get(container)) 21 , m_next_node(container.first_child()) 22 , m_layout_mode(layout_mode) 23{ 24 skip_to_next(); 25} 26 27void InlineLevelIterator::enter_node_with_box_model_metrics(Layout::NodeWithStyleAndBoxModelMetrics const& node) 28{ 29 if (!m_extra_leading_metrics.has_value()) 30 m_extra_leading_metrics = ExtraBoxMetrics {}; 31 32 // FIXME: It's really weird that *this* is where we assign box model metrics for these layout nodes.. 33 34 auto& used_values = m_layout_state.get_mutable(node); 35 auto const& computed_values = node.computed_values(); 36 37 used_values.margin_left = computed_values.margin().left().resolved(node, CSS::Length::make_px(m_container_state.content_width())).to_px(node); 38 used_values.border_left = computed_values.border_left().width; 39 used_values.padding_left = computed_values.padding().left().resolved(node, CSS::Length::make_px(m_container_state.content_width())).to_px(node); 40 41 m_extra_leading_metrics->margin += used_values.margin_left; 42 m_extra_leading_metrics->border += used_values.border_left; 43 m_extra_leading_metrics->padding += used_values.padding_left; 44 45 m_box_model_node_stack.append(node); 46} 47 48void InlineLevelIterator::exit_node_with_box_model_metrics() 49{ 50 if (!m_extra_trailing_metrics.has_value()) 51 m_extra_trailing_metrics = ExtraBoxMetrics {}; 52 53 auto& node = m_box_model_node_stack.last(); 54 auto& used_values = m_layout_state.get_mutable(node); 55 auto const& computed_values = node.computed_values(); 56 57 used_values.margin_right = computed_values.margin().right().resolved(node, CSS::Length::make_px(m_container_state.content_width())).to_px(node); 58 used_values.border_right = computed_values.border_right().width; 59 used_values.padding_right = computed_values.padding().right().resolved(node, CSS::Length::make_px(m_container_state.content_width())).to_px(node); 60 61 m_extra_trailing_metrics->margin += used_values.margin_right; 62 m_extra_trailing_metrics->border += used_values.border_right; 63 m_extra_trailing_metrics->padding += used_values.padding_right; 64 65 m_box_model_node_stack.take_last(); 66} 67 68// This is similar to Layout::Node::next_in_pre_order() but will not descend into inline-block nodes. 69Layout::Node const* InlineLevelIterator::next_inline_node_in_pre_order(Layout::Node const& current, Layout::Node const* stay_within) 70{ 71 if (current.first_child() 72 && current.first_child()->display().is_inline_outside() 73 && current.display().is_flow_inside() 74 && !current.is_replaced_box()) { 75 if (!current.is_box() || !static_cast<Box const&>(current).is_out_of_flow(m_inline_formatting_context)) 76 return current.first_child(); 77 } 78 79 Layout::Node const* node = &current; 80 Layout::Node const* next = nullptr; 81 while (!(next = node->next_sibling())) { 82 node = node->parent(); 83 84 // If node is the last node on the "box model node stack", pop it off. 85 if (!m_box_model_node_stack.is_empty() 86 && &m_box_model_node_stack.last() == node) { 87 exit_node_with_box_model_metrics(); 88 } 89 if (!node || node == stay_within) 90 return nullptr; 91 } 92 93 // If node is the last node on the "box model node stack", pop it off. 94 if (!m_box_model_node_stack.is_empty() 95 && &m_box_model_node_stack.last() == node) { 96 exit_node_with_box_model_metrics(); 97 } 98 99 return next; 100} 101 102void InlineLevelIterator::compute_next() 103{ 104 if (m_next_node == nullptr) 105 return; 106 do { 107 m_next_node = next_inline_node_in_pre_order(*m_next_node, &m_container); 108 } while (m_next_node && (!m_next_node->is_inline() && !m_next_node->is_out_of_flow(m_inline_formatting_context))); 109} 110 111void InlineLevelIterator::skip_to_next() 112{ 113 if (m_next_node 114 && is<Layout::NodeWithStyleAndBoxModelMetrics>(*m_next_node) 115 && m_next_node->display().is_flow_inside() 116 && !m_next_node->is_out_of_flow(m_inline_formatting_context) 117 && !m_next_node->is_replaced_box()) 118 enter_node_with_box_model_metrics(static_cast<Layout::NodeWithStyleAndBoxModelMetrics const&>(*m_next_node)); 119 120 m_current_node = m_next_node; 121 compute_next(); 122} 123 124Optional<InlineLevelIterator::Item> InlineLevelIterator::next(CSSPixels available_width) 125{ 126 if (!m_current_node) 127 return {}; 128 129 if (is<Layout::TextNode>(*m_current_node)) { 130 auto& text_node = static_cast<Layout::TextNode const&>(*m_current_node); 131 132 if (!m_text_node_context.has_value()) 133 enter_text_node(text_node); 134 135 auto chunk_opt = m_text_node_context->next_chunk; 136 if (!chunk_opt.has_value()) { 137 m_text_node_context = {}; 138 skip_to_next(); 139 return next(available_width); 140 } 141 142 m_text_node_context->next_chunk = m_text_node_context->chunk_iterator.next(); 143 if (!m_text_node_context->next_chunk.has_value()) 144 m_text_node_context->is_last_chunk = true; 145 146 auto& chunk = chunk_opt.value(); 147 CSSPixels chunk_width = text_node.font().width(chunk.view) + text_node.font().glyph_spacing(); 148 149 if (m_text_node_context->do_respect_linebreaks && chunk.has_breaking_newline) { 150 return Item { 151 .type = Item::Type::ForcedBreak, 152 }; 153 } 154 155 // NOTE: We never consider `content: ""` to be collapsible whitespace. 156 bool is_generated_empty_string = text_node.is_generated() && chunk.length == 0; 157 158 Item item { 159 .type = Item::Type::Text, 160 .node = &text_node, 161 .offset_in_node = chunk.start, 162 .length_in_node = chunk.length, 163 .width = chunk_width, 164 .is_collapsible_whitespace = m_text_node_context->do_collapse && chunk.is_all_whitespace && !is_generated_empty_string, 165 }; 166 167 add_extra_box_model_metrics_to_item(item, m_text_node_context->is_first_chunk, m_text_node_context->is_last_chunk); 168 return item; 169 } 170 171 if (m_current_node->is_absolutely_positioned()) { 172 auto& node = *m_current_node; 173 skip_to_next(); 174 return Item { 175 .type = Item::Type::AbsolutelyPositionedElement, 176 .node = &node, 177 }; 178 } 179 180 if (m_current_node->is_floating()) { 181 auto& node = *m_current_node; 182 skip_to_next(); 183 return Item { 184 .type = Item::Type::FloatingElement, 185 .node = &node, 186 }; 187 } 188 189 if (is<Layout::BreakNode>(*m_current_node)) { 190 skip_to_next(); 191 return Item { 192 .type = Item::Type::ForcedBreak, 193 }; 194 } 195 196 if (is<Layout::ListItemMarkerBox>(*m_current_node)) { 197 skip_to_next(); 198 return next(available_width); 199 } 200 201 if (!is<Layout::Box>(*m_current_node)) { 202 skip_to_next(); 203 return next(available_width); 204 } 205 206 if (is<Layout::ReplacedBox>(*m_current_node)) { 207 auto& replaced_box = static_cast<Layout::ReplacedBox const&>(*m_current_node); 208 // FIXME: This const_cast is gross. 209 const_cast<Layout::ReplacedBox&>(replaced_box).prepare_for_replaced_layout(); 210 } 211 212 auto& box = verify_cast<Layout::Box>(*m_current_node); 213 auto& box_state = m_layout_state.get(box); 214 m_inline_formatting_context.dimension_box_on_line(box, m_layout_mode); 215 216 skip_to_next(); 217 auto item = Item { 218 .type = Item::Type::Element, 219 .node = &box, 220 .offset_in_node = 0, 221 .length_in_node = 0, 222 .width = box_state.content_width(), 223 .padding_start = box_state.padding_left, 224 .padding_end = box_state.padding_right, 225 .border_start = box_state.border_left, 226 .border_end = box_state.border_right, 227 .margin_start = box_state.margin_left, 228 .margin_end = box_state.margin_right, 229 }; 230 add_extra_box_model_metrics_to_item(item, true, true); 231 return item; 232} 233 234void InlineLevelIterator::enter_text_node(Layout::TextNode const& text_node) 235{ 236 bool do_collapse = true; 237 bool do_wrap_lines = true; 238 bool do_respect_linebreaks = false; 239 240 if (text_node.computed_values().white_space() == CSS::WhiteSpace::Nowrap) { 241 do_collapse = true; 242 do_wrap_lines = false; 243 do_respect_linebreaks = false; 244 } else if (text_node.computed_values().white_space() == CSS::WhiteSpace::Pre) { 245 do_collapse = false; 246 do_wrap_lines = false; 247 do_respect_linebreaks = true; 248 } else if (text_node.computed_values().white_space() == CSS::WhiteSpace::PreLine) { 249 do_collapse = true; 250 do_wrap_lines = true; 251 do_respect_linebreaks = true; 252 } else if (text_node.computed_values().white_space() == CSS::WhiteSpace::PreWrap) { 253 do_collapse = false; 254 do_wrap_lines = true; 255 do_respect_linebreaks = true; 256 } 257 258 if (text_node.dom_node().is_editable() && !text_node.dom_node().is_uninteresting_whitespace_node()) 259 do_collapse = false; 260 261 // FIXME: The const_cast here is gross. 262 const_cast<TextNode&>(text_node).compute_text_for_rendering(do_collapse); 263 264 m_text_node_context = TextNodeContext { 265 .do_collapse = do_collapse, 266 .do_wrap_lines = do_wrap_lines, 267 .do_respect_linebreaks = do_respect_linebreaks, 268 .is_first_chunk = true, 269 .is_last_chunk = false, 270 .chunk_iterator = TextNode::ChunkIterator { text_node.text_for_rendering(), do_wrap_lines, do_respect_linebreaks, text_node.is_generated() && text_node.text_for_rendering().is_empty() }, 271 }; 272 m_text_node_context->next_chunk = m_text_node_context->chunk_iterator.next(); 273} 274 275void InlineLevelIterator::add_extra_box_model_metrics_to_item(Item& item, bool add_leading_metrics, bool add_trailing_metrics) 276{ 277 if (add_leading_metrics && m_extra_leading_metrics.has_value()) { 278 item.margin_start += m_extra_leading_metrics->margin; 279 item.border_start += m_extra_leading_metrics->border; 280 item.padding_start += m_extra_leading_metrics->padding; 281 m_extra_leading_metrics = {}; 282 } 283 284 if (add_trailing_metrics && m_extra_trailing_metrics.has_value()) { 285 item.margin_end += m_extra_trailing_metrics->margin; 286 item.border_end += m_extra_trailing_metrics->border; 287 item.padding_end += m_extra_trailing_metrics->padding; 288 m_extra_trailing_metrics = {}; 289 } 290} 291 292}