/src/serenity/Userland/Libraries/LibWeb/Layout/InlineFormattingContext.cpp
Line | Count | Source |
1 | | /* |
2 | | * Copyright (c) 2020-2024, Andreas Kling <kling@serenityos.org> |
3 | | * |
4 | | * SPDX-License-Identifier: BSD-2-Clause |
5 | | */ |
6 | | |
7 | | #include <LibWeb/CSS/Length.h> |
8 | | #include <LibWeb/DOM/Node.h> |
9 | | #include <LibWeb/Dump.h> |
10 | | #include <LibWeb/Layout/BlockContainer.h> |
11 | | #include <LibWeb/Layout/BlockFormattingContext.h> |
12 | | #include <LibWeb/Layout/Box.h> |
13 | | #include <LibWeb/Layout/InlineFormattingContext.h> |
14 | | #include <LibWeb/Layout/InlineLevelIterator.h> |
15 | | #include <LibWeb/Layout/LineBuilder.h> |
16 | | #include <LibWeb/Layout/ReplacedBox.h> |
17 | | #include <LibWeb/Layout/SVGSVGBox.h> |
18 | | |
19 | | namespace Web::Layout { |
20 | | |
21 | | InlineFormattingContext::InlineFormattingContext( |
22 | | LayoutState& state, |
23 | | LayoutMode layout_mode, |
24 | | BlockContainer const& containing_block, |
25 | | LayoutState::UsedValues& containing_block_used_values, |
26 | | BlockFormattingContext& parent) |
27 | 0 | : FormattingContext(Type::Inline, layout_mode, state, containing_block, &parent) |
28 | 0 | , m_containing_block_used_values(containing_block_used_values) |
29 | 0 | { |
30 | 0 | } |
31 | | |
32 | 0 | InlineFormattingContext::~InlineFormattingContext() = default; |
33 | | |
34 | | BlockFormattingContext& InlineFormattingContext::parent() |
35 | 0 | { |
36 | 0 | return static_cast<BlockFormattingContext&>(*FormattingContext::parent()); |
37 | 0 | } |
38 | | |
39 | | BlockFormattingContext const& InlineFormattingContext::parent() const |
40 | 0 | { |
41 | 0 | return static_cast<BlockFormattingContext const&>(*FormattingContext::parent()); |
42 | 0 | } |
43 | | |
44 | | CSSPixels InlineFormattingContext::leftmost_x_offset_at(CSSPixels y) const |
45 | 0 | { |
46 | | // NOTE: Floats are relative to the BFC root box, not necessarily the containing block of this IFC. |
47 | 0 | auto box_in_root_rect = content_box_rect_in_ancestor_coordinate_space(m_containing_block_used_values, parent().root()); |
48 | 0 | CSSPixels y_in_root = box_in_root_rect.y() + y; |
49 | 0 | auto space_and_containing_margin = parent().space_used_and_containing_margin_for_floats(y_in_root); |
50 | 0 | auto left_side_floats_limit_to_right = space_and_containing_margin.left_total_containing_margin + space_and_containing_margin.left_used_space; |
51 | 0 | if (box_in_root_rect.x() >= left_side_floats_limit_to_right) { |
52 | | // The left edge of the containing block is to the right of the rightmost left-side float. |
53 | | // We start placing inline content at the left edge of the containing block. |
54 | 0 | return 0; |
55 | 0 | } |
56 | | // The left edge of the containing block is to the left of the rightmost left-side float. |
57 | | // We adjust the inline content insertion point by the overlap between the containing block and the float. |
58 | 0 | return left_side_floats_limit_to_right - max(CSSPixels(0), box_in_root_rect.x()); |
59 | 0 | } |
60 | | |
61 | | AvailableSize InlineFormattingContext::available_space_for_line(CSSPixels y) const |
62 | 0 | { |
63 | 0 | auto intrusions = parent().intrusion_by_floats_into_box(m_containing_block_used_values, y); |
64 | 0 | if (m_available_space->width.is_definite()) { |
65 | 0 | return AvailableSize::make_definite(m_available_space->width.to_px_or_zero() - (intrusions.left + intrusions.right)); |
66 | 0 | } else { |
67 | 0 | return m_available_space->width; |
68 | 0 | } |
69 | 0 | } |
70 | | |
71 | | CSSPixels InlineFormattingContext::automatic_content_width() const |
72 | 0 | { |
73 | 0 | return m_automatic_content_width; |
74 | 0 | } |
75 | | |
76 | | CSSPixels InlineFormattingContext::automatic_content_height() const |
77 | 0 | { |
78 | 0 | return m_automatic_content_height; |
79 | 0 | } |
80 | | |
81 | | void InlineFormattingContext::run(AvailableSpace const& available_space) |
82 | 0 | { |
83 | 0 | VERIFY(containing_block().children_are_inline()); |
84 | 0 | m_available_space = available_space; |
85 | 0 | generate_line_boxes(); |
86 | |
|
87 | 0 | CSSPixels content_height = 0; |
88 | |
|
89 | 0 | for (auto& line_box : m_containing_block_used_values.line_boxes) { |
90 | 0 | content_height += line_box.height(); |
91 | 0 | } |
92 | | |
93 | | // NOTE: We ask the parent BFC to calculate the automatic content width of this IFC. |
94 | | // This ensures that any floated boxes are taken into account. |
95 | 0 | m_automatic_content_width = parent().greatest_child_width(containing_block()); |
96 | 0 | m_automatic_content_height = content_height; |
97 | 0 | } |
98 | | |
99 | | void InlineFormattingContext::dimension_box_on_line(Box const& box, LayoutMode layout_mode) |
100 | 0 | { |
101 | 0 | auto width_of_containing_block = m_available_space->width.to_px_or_zero(); |
102 | 0 | auto& box_state = m_state.get_mutable(box); |
103 | 0 | auto const& computed_values = box.computed_values(); |
104 | |
|
105 | 0 | box_state.margin_left = computed_values.margin().left().to_px(box, width_of_containing_block); |
106 | 0 | box_state.border_left = computed_values.border_left().width; |
107 | 0 | box_state.padding_left = computed_values.padding().left().to_px(box, width_of_containing_block); |
108 | |
|
109 | 0 | box_state.margin_right = computed_values.margin().right().to_px(box, width_of_containing_block); |
110 | 0 | box_state.border_right = computed_values.border_right().width; |
111 | 0 | box_state.padding_right = computed_values.padding().right().to_px(box, width_of_containing_block); |
112 | |
|
113 | 0 | box_state.margin_top = computed_values.margin().top().to_px(box, width_of_containing_block); |
114 | 0 | box_state.border_top = computed_values.border_top().width; |
115 | 0 | box_state.padding_top = computed_values.padding().top().to_px(box, width_of_containing_block); |
116 | |
|
117 | 0 | box_state.padding_bottom = computed_values.padding().bottom().to_px(box, width_of_containing_block); |
118 | 0 | box_state.border_bottom = computed_values.border_bottom().width; |
119 | 0 | box_state.margin_bottom = computed_values.margin().bottom().to_px(box, width_of_containing_block); |
120 | |
|
121 | 0 | if (box_is_sized_as_replaced_element(box)) { |
122 | 0 | box_state.set_content_width(compute_width_for_replaced_element(box, *m_available_space)); |
123 | 0 | box_state.set_content_height(compute_height_for_replaced_element(box, *m_available_space)); |
124 | 0 | auto independent_formatting_context = layout_inside(box, layout_mode, box_state.available_inner_space_or_constraints_from(*m_available_space)); |
125 | 0 | if (independent_formatting_context) |
126 | 0 | independent_formatting_context->parent_context_did_dimension_child_root_box(); |
127 | 0 | return; |
128 | 0 | } |
129 | | |
130 | | // Any box that has simple flow inside should have generated line box fragments already. |
131 | 0 | if (box.display().is_flow_inside()) { |
132 | 0 | dbgln("FIXME: InlineFormattingContext::dimension_box_on_line got unexpected box in inline context:"); |
133 | 0 | dump_tree(box); |
134 | 0 | return; |
135 | 0 | } |
136 | | |
137 | 0 | auto const& width_value = box.computed_values().width(); |
138 | 0 | CSSPixels unconstrained_width = 0; |
139 | 0 | if (should_treat_width_as_auto(box, *m_available_space)) { |
140 | 0 | auto result = calculate_shrink_to_fit_widths(box); |
141 | |
|
142 | 0 | if (m_available_space->width.is_definite()) { |
143 | 0 | auto available_width = m_available_space->width.to_px_or_zero() |
144 | 0 | - box_state.margin_left |
145 | 0 | - box_state.border_left |
146 | 0 | - box_state.padding_left |
147 | 0 | - box_state.padding_right |
148 | 0 | - box_state.border_right |
149 | 0 | - box_state.margin_right; |
150 | |
|
151 | 0 | unconstrained_width = min(max(result.preferred_minimum_width, available_width), result.preferred_width); |
152 | 0 | } else if (m_available_space->width.is_min_content()) { |
153 | 0 | unconstrained_width = result.preferred_minimum_width; |
154 | 0 | } else { |
155 | 0 | unconstrained_width = result.preferred_width; |
156 | 0 | } |
157 | 0 | } else { |
158 | 0 | if (width_value.contains_percentage() && !m_available_space->width.is_definite()) { |
159 | | // NOTE: We can't resolve percentages yet. We'll have to wait until after inner layout. |
160 | 0 | } else { |
161 | 0 | auto inner_width = calculate_inner_width(box, m_available_space->width, width_value); |
162 | 0 | unconstrained_width = inner_width; |
163 | 0 | } |
164 | 0 | } |
165 | |
|
166 | 0 | CSSPixels width = unconstrained_width; |
167 | 0 | if (!should_treat_max_width_as_none(box, m_available_space->width)) { |
168 | 0 | auto max_width = calculate_inner_width(box, m_available_space->width, box.computed_values().max_width()); |
169 | 0 | width = min(width, max_width); |
170 | 0 | } |
171 | |
|
172 | 0 | auto computed_min_width = box.computed_values().min_width(); |
173 | 0 | if (!computed_min_width.is_auto()) { |
174 | 0 | auto min_width = calculate_inner_width(box, m_available_space->width, computed_min_width); |
175 | 0 | width = max(width, min_width); |
176 | 0 | } |
177 | |
|
178 | 0 | box_state.set_content_width(width); |
179 | |
|
180 | 0 | parent().resolve_used_height_if_not_treated_as_auto(box, AvailableSpace(AvailableSize::make_definite(width), AvailableSize::make_indefinite())); |
181 | | |
182 | | // NOTE: Flex containers with `auto` height are treated as `max-content`, so we can compute their height early. |
183 | 0 | if (box.display().is_flex_inside()) |
184 | 0 | parent().resolve_used_height_if_treated_as_auto(box, AvailableSpace(AvailableSize::make_definite(width), AvailableSize::make_indefinite())); |
185 | |
|
186 | 0 | auto independent_formatting_context = layout_inside(box, layout_mode, box_state.available_inner_space_or_constraints_from(*m_available_space)); |
187 | |
|
188 | 0 | auto const& height_value = box.computed_values().height(); |
189 | 0 | if (should_treat_height_as_auto(box, *m_available_space)) { |
190 | | // FIXME: (10.6.6) If 'height' is 'auto', the height depends on the element's descendants per 10.6.7. |
191 | 0 | parent().resolve_used_height_if_treated_as_auto(box, AvailableSpace(AvailableSize::make_indefinite(), AvailableSize::make_indefinite())); |
192 | 0 | } else { |
193 | 0 | auto inner_height = calculate_inner_height(box, AvailableSize::make_definite(m_containing_block_used_values.content_height()), height_value); |
194 | 0 | box_state.set_content_height(inner_height); |
195 | 0 | } |
196 | |
|
197 | 0 | if (independent_formatting_context) |
198 | 0 | independent_formatting_context->parent_context_did_dimension_child_root_box(); |
199 | 0 | } |
200 | | |
201 | | void InlineFormattingContext::apply_justification_to_fragments(CSS::TextJustify text_justify, LineBox& line_box, bool is_last_line) |
202 | 0 | { |
203 | 0 | switch (text_justify) { |
204 | 0 | case CSS::TextJustify::None: |
205 | 0 | return; |
206 | | // FIXME: These two cases currently fall back to auto, handle them as well. |
207 | 0 | case CSS::TextJustify::InterCharacter: |
208 | 0 | case CSS::TextJustify::InterWord: |
209 | 0 | case CSS::TextJustify::Auto: |
210 | 0 | break; |
211 | 0 | } |
212 | | |
213 | | // https://www.w3.org/TR/css-text-3/#text-align-property |
214 | | // Unless otherwise specified by text-align-last, the last line before a forced break or the end of the block is start-aligned. |
215 | | // FIXME: Support text-align-last. |
216 | 0 | if (is_last_line || line_box.m_has_forced_break) |
217 | 0 | return; |
218 | | |
219 | 0 | CSSPixels excess_horizontal_space = line_box.original_available_width().to_px_or_zero() - line_box.width(); |
220 | 0 | CSSPixels excess_horizontal_space_including_whitespace = excess_horizontal_space; |
221 | 0 | size_t whitespace_count = 0; |
222 | 0 | for (auto& fragment : line_box.fragments()) { |
223 | 0 | if (fragment.is_justifiable_whitespace()) { |
224 | 0 | ++whitespace_count; |
225 | 0 | excess_horizontal_space_including_whitespace += fragment.width(); |
226 | 0 | } |
227 | 0 | } |
228 | |
|
229 | 0 | CSSPixels justified_space_width = whitespace_count > 0 ? (excess_horizontal_space_including_whitespace / whitespace_count) : 0; |
230 | | |
231 | | // This is the amount that each fragment will be offset by. If a whitespace |
232 | | // fragment is shorter than the justified space width, it increases to push |
233 | | // subsequent fragments, and decreases to pull them back otherwise. |
234 | 0 | CSSPixels running_diff = 0; |
235 | 0 | for (size_t i = 0; i < line_box.fragments().size(); ++i) { |
236 | 0 | auto& fragment = line_box.fragments()[i]; |
237 | |
|
238 | 0 | auto offset = fragment.offset(); |
239 | 0 | offset.translate_by(running_diff, 0); |
240 | 0 | fragment.set_offset(offset); |
241 | |
|
242 | 0 | if (fragment.is_justifiable_whitespace() |
243 | 0 | && fragment.width() != justified_space_width) { |
244 | 0 | running_diff += justified_space_width - fragment.width(); |
245 | 0 | fragment.set_width(justified_space_width); |
246 | 0 | } |
247 | 0 | } |
248 | 0 | } |
249 | | |
250 | | void InlineFormattingContext::generate_line_boxes() |
251 | 0 | { |
252 | 0 | auto& line_boxes = m_containing_block_used_values.line_boxes; |
253 | 0 | line_boxes.clear_with_capacity(); |
254 | |
|
255 | 0 | auto direction = m_context_box->computed_values().direction(); |
256 | |
|
257 | 0 | InlineLevelIterator iterator(*this, m_state, containing_block(), m_containing_block_used_values, m_layout_mode); |
258 | 0 | LineBuilder line_builder(*this, m_state, m_containing_block_used_values, direction); |
259 | | |
260 | | // NOTE: When we ignore collapsible whitespace chunks at the start of a line, |
261 | | // we have to remember how much start margin that chunk had in the inline |
262 | | // axis, so that we can add it to the first non-whitespace chunk. |
263 | 0 | CSSPixels leading_margin_from_collapsible_whitespace = 0; |
264 | |
|
265 | 0 | for (;;) { |
266 | 0 | auto item_opt = iterator.next(); |
267 | 0 | if (!item_opt.has_value()) |
268 | 0 | break; |
269 | 0 | auto& item = item_opt.value(); |
270 | | |
271 | | // Ignore collapsible whitespace chunks at the start of line, and if the last fragment already ends in whitespace. |
272 | 0 | if (item.is_collapsible_whitespace && (line_boxes.is_empty() || line_boxes.last().is_empty_or_ends_in_whitespace())) { |
273 | 0 | if (item.node->computed_values().white_space() != CSS::WhiteSpace::Nowrap) { |
274 | 0 | auto next_width = iterator.next_non_whitespace_sequence_width(); |
275 | 0 | if (next_width > 0) |
276 | 0 | line_builder.break_if_needed(next_width); |
277 | 0 | } |
278 | 0 | leading_margin_from_collapsible_whitespace += item.margin_start; |
279 | 0 | continue; |
280 | 0 | } |
281 | | |
282 | 0 | item.margin_start += leading_margin_from_collapsible_whitespace; |
283 | 0 | leading_margin_from_collapsible_whitespace = 0; |
284 | |
|
285 | 0 | switch (item.type) { |
286 | 0 | case InlineLevelIterator::Item::Type::ForcedBreak: { |
287 | 0 | line_builder.break_line(LineBuilder::ForcedBreak::Yes); |
288 | 0 | if (item.node) { |
289 | 0 | auto introduce_clearance = parent().clear_floating_boxes(*item.node, *this); |
290 | 0 | if (introduce_clearance == BlockFormattingContext::DidIntroduceClearance::Yes) |
291 | 0 | parent().reset_margin_state(); |
292 | 0 | } |
293 | 0 | break; |
294 | 0 | } |
295 | 0 | case InlineLevelIterator::Item::Type::Element: { |
296 | 0 | auto& box = verify_cast<Layout::Box>(*item.node); |
297 | 0 | compute_inset(box); |
298 | 0 | if (containing_block().computed_values().white_space() != CSS::WhiteSpace::Nowrap) { |
299 | 0 | auto minimum_space_needed_on_line = item.border_box_width(); |
300 | 0 | if (item.margin_start < 0) |
301 | 0 | minimum_space_needed_on_line += item.margin_start; |
302 | 0 | if (item.margin_end < 0) |
303 | 0 | minimum_space_needed_on_line += item.margin_end; |
304 | 0 | line_builder.break_if_needed(minimum_space_needed_on_line); |
305 | 0 | } |
306 | 0 | line_builder.append_box(box, item.border_start + item.padding_start, item.padding_end + item.border_end, item.margin_start, item.margin_end); |
307 | 0 | break; |
308 | 0 | } |
309 | 0 | case InlineLevelIterator::Item::Type::AbsolutelyPositionedElement: |
310 | 0 | if (is<Box>(*item.node)) |
311 | 0 | parent().add_absolutely_positioned_box(static_cast<Layout::Box const&>(*item.node)); |
312 | 0 | break; |
313 | | |
314 | 0 | case InlineLevelIterator::Item::Type::FloatingElement: |
315 | 0 | if (is<Box>(*item.node)) { |
316 | 0 | [[maybe_unused]] auto introduce_clearance = parent().clear_floating_boxes(*item.node, *this); |
317 | | // Even if this introduces clearance, we do NOT reset |
318 | | // the margin state, because that is clearance between |
319 | | // floats and does not contribute to the height of the |
320 | | // Inline Formatting Context. |
321 | 0 | parent().layout_floating_box(static_cast<Layout::Box const&>(*item.node), containing_block(), *m_available_space, 0, &line_builder); |
322 | 0 | } |
323 | 0 | break; |
324 | | |
325 | 0 | case InlineLevelIterator::Item::Type::Text: { |
326 | 0 | auto& text_node = verify_cast<Layout::TextNode>(*item.node); |
327 | |
|
328 | 0 | if (text_node.computed_values().white_space() != CSS::WhiteSpace::Nowrap) { |
329 | 0 | bool is_whitespace = false; |
330 | 0 | CSSPixels next_width = 0; |
331 | | // If we're in a whitespace-collapsing context, we can simply check the flag. |
332 | 0 | if (item.is_collapsible_whitespace) { |
333 | 0 | is_whitespace = true; |
334 | 0 | next_width = iterator.next_non_whitespace_sequence_width(); |
335 | 0 | } else { |
336 | | // In whitespace-preserving contexts (white-space: pre*), we have to check manually. |
337 | 0 | auto view = text_node.text_for_rendering().bytes_as_string_view().substring_view(item.offset_in_node, item.length_in_node); |
338 | 0 | is_whitespace = view.is_whitespace(); |
339 | 0 | if (is_whitespace) |
340 | 0 | next_width = iterator.next_non_whitespace_sequence_width(); |
341 | 0 | } |
342 | | |
343 | | // If whitespace caused us to break, we swallow the whitespace instead of |
344 | | // putting it on the next line. |
345 | 0 | if (is_whitespace && next_width > 0 && line_builder.break_if_needed(item.border_box_width() + next_width)) |
346 | 0 | break; |
347 | 0 | } else if (text_node.computed_values().text_overflow() == CSS::TextOverflow::Ellipsis |
348 | 0 | && text_node.computed_values().overflow_x() != CSS::Overflow::Visible) { |
349 | | // We may need to do an ellipsis if the text is too long for the container |
350 | 0 | constexpr u32 ellipsis_codepoint = 0x2026; |
351 | 0 | if (m_available_space.has_value() |
352 | 0 | && item.width.to_double() > m_available_space.value().width.to_px_or_zero().to_double()) { |
353 | | // Do the ellipsis |
354 | 0 | auto& glyph_run = item.glyph_run; |
355 | |
|
356 | 0 | auto available_width = m_available_space.value().width.to_px_or_zero().to_float(); |
357 | 0 | auto ellipsis_width = glyph_run->font().glyph_width(ellipsis_codepoint); |
358 | 0 | auto max_text_width = available_width - ellipsis_width; |
359 | |
|
360 | 0 | auto& glyphs = glyph_run->glyphs(); |
361 | 0 | size_t last_glyph_index = 0; |
362 | 0 | auto last_glyph_position = Gfx::FloatPoint(); |
363 | |
|
364 | 0 | for (auto const& glyph_or_emoji : glyphs) { |
365 | 0 | auto this_position = Gfx::FloatPoint(); |
366 | 0 | glyph_or_emoji.visit( |
367 | 0 | [&](Gfx::DrawGlyph glyph) { |
368 | 0 | this_position = glyph.position; |
369 | 0 | }, |
370 | 0 | [&](Gfx::DrawEmoji emoji) { |
371 | 0 | this_position = emoji.position; |
372 | 0 | }); |
373 | 0 | if (this_position.x() > max_text_width) |
374 | 0 | break; |
375 | | |
376 | 0 | last_glyph_index++; |
377 | 0 | last_glyph_position = this_position; |
378 | 0 | } |
379 | |
|
380 | 0 | if (last_glyph_index > 1) { |
381 | 0 | auto remove_item_count = glyphs.size() - last_glyph_index; |
382 | 0 | glyphs.remove(last_glyph_index - 1, remove_item_count); |
383 | 0 | glyphs.append(Gfx::DrawGlyph { |
384 | 0 | .position = last_glyph_position, |
385 | 0 | .code_point = ellipsis_codepoint }); |
386 | 0 | } |
387 | 0 | } |
388 | 0 | } |
389 | 0 | line_builder.append_text_chunk( |
390 | 0 | text_node, |
391 | 0 | item.offset_in_node, |
392 | 0 | item.length_in_node, |
393 | 0 | item.border_start + item.padding_start, |
394 | 0 | item.padding_end + item.border_end, |
395 | 0 | item.margin_start, |
396 | 0 | item.margin_end, |
397 | 0 | item.width, |
398 | 0 | text_node.computed_values().line_height(), |
399 | 0 | move(item.glyph_run)); |
400 | 0 | break; |
401 | 0 | } |
402 | 0 | } |
403 | 0 | } |
404 | | |
405 | 0 | for (auto& line_box : line_boxes) { |
406 | 0 | line_box.trim_trailing_whitespace(); |
407 | 0 | } |
408 | |
|
409 | 0 | line_builder.remove_last_line_if_empty(); |
410 | |
|
411 | 0 | auto const& containing_block = this->containing_block(); |
412 | 0 | auto text_align = containing_block.computed_values().text_align(); |
413 | 0 | auto text_justify = containing_block.computed_values().text_justify(); |
414 | 0 | if (text_align == CSS::TextAlign::Justify) { |
415 | 0 | for (size_t i = 0; i < line_boxes.size(); i++) { |
416 | 0 | auto& line_box = line_boxes[i]; |
417 | 0 | auto is_last_line = i == line_boxes.size() - 1; |
418 | 0 | apply_justification_to_fragments(text_justify, line_box, is_last_line); |
419 | 0 | } |
420 | 0 | } |
421 | 0 | } |
422 | | |
423 | | bool InlineFormattingContext::any_floats_intrude_at_y(CSSPixels y) const |
424 | 0 | { |
425 | 0 | auto box_in_root_rect = content_box_rect_in_ancestor_coordinate_space(m_containing_block_used_values, parent().root()); |
426 | 0 | CSSPixels y_in_root = box_in_root_rect.y() + y; |
427 | 0 | auto space_and_containing_margin = parent().space_used_and_containing_margin_for_floats(y_in_root); |
428 | 0 | return space_and_containing_margin.left_used_space > 0 || space_and_containing_margin.right_used_space > 0; |
429 | 0 | } |
430 | | |
431 | | bool InlineFormattingContext::can_fit_new_line_at_y(CSSPixels y) const |
432 | 0 | { |
433 | 0 | auto top_intrusions = parent().intrusion_by_floats_into_box(m_containing_block_used_values, y); |
434 | 0 | auto bottom_intrusions = parent().intrusion_by_floats_into_box(m_containing_block_used_values, y + containing_block().computed_values().line_height() - 1); |
435 | |
|
436 | 0 | auto left_edge = [](auto& space) -> CSSPixels { |
437 | 0 | return space.left; |
438 | 0 | }; |
439 | |
|
440 | 0 | auto right_edge = [this](auto& space) -> CSSPixels { |
441 | 0 | return m_available_space->width.to_px_or_zero() - space.right; |
442 | 0 | }; |
443 | |
|
444 | 0 | auto top_left_edge = left_edge(top_intrusions); |
445 | 0 | auto top_right_edge = right_edge(top_intrusions); |
446 | 0 | auto bottom_left_edge = left_edge(bottom_intrusions); |
447 | 0 | auto bottom_right_edge = right_edge(bottom_intrusions); |
448 | |
|
449 | 0 | if (top_left_edge > bottom_right_edge) |
450 | 0 | return false; |
451 | 0 | if (bottom_left_edge > top_right_edge) |
452 | 0 | return false; |
453 | 0 | return true; |
454 | 0 | } |
455 | | |
456 | | CSSPixels InlineFormattingContext::vertical_float_clearance() const |
457 | 0 | { |
458 | 0 | return m_vertical_float_clearance; |
459 | 0 | } |
460 | | |
461 | | void InlineFormattingContext::set_vertical_float_clearance(CSSPixels vertical_float_clearance) |
462 | 0 | { |
463 | 0 | m_vertical_float_clearance = vertical_float_clearance; |
464 | 0 | } |
465 | | } |