Skip to main content

layout/flow/inline/
mod.rs

1/* This Source Code Form is subject to the terms of the Mozilla Public
2 * License, v. 2.0. If a copy of the MPL was not distributed with this
3 * file, You can obtain one at https://mozilla.org/MPL/2.0/. */
4
5//! # Inline Formatting Context Layout
6//!
7//! Inline layout is divided into three phases:
8//!
9//! 1. Box Tree Construction
10//! 2. Box to Line Layout
11//! 3. Line to Fragment Layout
12//!
13//! The first phase happens during normal box tree constrution, while the second two phases happen
14//! during fragment tree construction (sometimes called just "layout").
15//!
16//! ## Box Tree Construction
17//!
18//! During box tree construction, DOM elements are transformed into a box tree. This phase collects
19//! all of the inline boxes, text, atomic inline elements (boxes with `display: inline-block` or
20//! `display: inline-table` as well as things like images and canvas), absolutely positioned blocks,
21//! and floated blocks.
22//!
23//! During the last part of this phase, whitespace is collapsed and text is segmented into
24//! [`TextRun`]s based on script, chosen font, and line breaking opportunities. In addition, default
25//! fonts are selected for every inline box. Each segment of text is shaped using HarfBuzz and
26//! turned into a series of glyphs, which all have a size and a position relative to the origin of
27//! the [`TextRun`] (calculated in later phases).
28//!
29//! The code for this phase is mainly in `construct.rs`, but text handling can also be found in
30//! `text_runs.rs.`
31//!
32//! ## Box to Line Layout
33//!
34//! During the first phase of fragment tree construction, box tree items are laid out into
35//! [`LineItem`]s and fragmented based on line boundaries. This is where line breaking happens. This
36//! part of layout fragments boxes and their contents across multiple lines while positioning floats
37//! and making sure non-floated contents flow around them. In addition, all atomic elements are laid
38//! out, which may descend into their respective trees and create fragments. Finally, absolutely
39//! positioned content is collected in order to later hoist it to the containing block for
40//! absolutes.
41//!
42//! Note that during this phase, layout does not know the final block position of content. Only
43//! during line to fragment layout, are the final block positions calculated based on the line's
44//! final content and its vertical alignment. Instead, positions and line heights are calculated
45//! relative to the line's final baseline which will be determined in the final phase.
46//!
47//! [`LineItem`]s represent a particular set of content on a line. Currently this is represented by
48//! a linear series of items that describe the line's hierarchy of inline boxes and content. The
49//! item types are:
50//!
51//!  - [`LineItem::InlineStartBoxPaddingBorderMargin`]
52//!  - [`LineItem::InlineEndBoxPaddingBorderMargin`]
53//!  - [`LineItem::TextRun`]
54//!  - [`LineItem::Atomic`]
55//!  - [`LineItem::AbsolutelyPositioned`]
56//!  - [`LineItem::Float`]
57//!
58//! The code for this can be found by looking for methods of the form `layout_into_line_item()`.
59//!
60//! ## Line to Fragment Layout
61//!
62//! During the second phase of fragment tree construction, the final block position of [`LineItem`]s
63//! is calculated and they are converted into [`Fragment`]s. After layout, the [`LineItem`]s are
64//! discarded and the new fragments are incorporated into the fragment tree. The final static
65//! position of absolutely positioned content is calculated and it is hoisted to its containing
66//! block via [`PositioningContext`].
67//!
68//! The code for this phase, can mainly be found in `line.rs`.
69//!
70
71pub mod construct;
72pub mod inline_box;
73pub mod line;
74mod line_breaker;
75pub mod text_run;
76
77use std::cell::{OnceCell, RefCell};
78use std::mem;
79use std::rc::Rc;
80use std::sync::{Arc, OnceLock};
81
82use app_units::{Au, MAX_AU};
83use atomic_refcell::AtomicRef;
84use bitflags::bitflags;
85use construct::InlineFormattingContextBuilder;
86use fonts::{FontMetrics, FontRef, ShapedTextSlice};
87use icu_locid::LanguageIdentifier;
88use icu_locid::subtags::{Language, language};
89use icu_properties::{self, LineBreak as ICULineBreak};
90use icu_segmenter::{LineBreakOptions, LineBreakStrictness, LineBreakWordOption};
91use inline_box::{InlineBox, InlineBoxContainerState, InlineBoxIdentifier, InlineBoxes};
92use layout_api::{LayoutNode, SharedSelection};
93use line::{
94    AbsolutelyPositionedLineItem, AtomicLineItem, FloatLineItem, LineItem, LineItemLayout,
95    TextRunLineItem,
96};
97use line_breaker::LineBreaker;
98use malloc_size_of_derive::MallocSizeOf;
99use script::layout_dom::ServoLayoutNode;
100use servo_arc::Arc as ServoArc;
101use style::Zero;
102use style::computed_values::line_break::T as LineBreak;
103use style::computed_values::text_wrap_mode::T as TextWrapMode;
104use style::computed_values::white_space_collapse::T as WhiteSpaceCollapse;
105use style::computed_values::word_break::T as WordBreak;
106use style::context::{QuirksMode, SharedStyleContext};
107use style::properties::ComputedValues;
108use style::properties::style_structs::InheritedText;
109use style::values::computed::BaselineShift;
110use style::values::generics::box_::BaselineShiftKeyword;
111use style::values::generics::font::LineHeight;
112use style::values::specified::box_::BaselineSource;
113use style::values::specified::text::TextAlignKeyword;
114use style::values::specified::{AlignmentBaseline, TextAlignLast, TextJustify};
115use text_run::{TextRun, get_font_for_first_font_for_style};
116use unicode_bidi::{BidiInfo, Level};
117
118use super::float::{Clear, PlacementAmongFloats};
119use super::{IndependentFloatOrAtomicLayoutResult, IndependentFormattingContextLayoutResult};
120use crate::cell::{ArcRefCell, WeakRefCell};
121use crate::context::LayoutContext;
122use crate::dom::WeakLayoutBox;
123use crate::dom_traversal::NodeAndStyleInfo;
124use crate::flow::float::{FloatBox, SequentialLayoutState};
125use crate::flow::inline::line::TextRunOffsets;
126use crate::flow::inline::text_run::{FontAndScriptInfo, TextRunItem, TextRunSegment};
127use crate::flow::{
128    BlockLevelBox, CollapsibleWithParentStartMargin, FloatSide, PlacementState,
129    compute_inline_content_sizes_for_block_level_boxes, layout_block_level_child,
130};
131use crate::formatting_contexts::{Baselines, IndependentFormattingContext};
132use crate::fragment_tree::{
133    BaseFragmentInfo, BoxFragment, CollapsedMargin, Fragment, FragmentFlags, PositioningFragment,
134};
135use crate::geom::{LogicalRect, LogicalSides1D, LogicalVec2, ToLogical};
136use crate::layout_box_base::LayoutBoxBase;
137use crate::positioned::{AbsolutelyPositionedBox, PositioningContext};
138use crate::sizing::{ComputeInlineContentSizes, ContentSizes, InlineContentSizesResult};
139use crate::style_ext::{ComputedValuesExt, PaddingBorderMargin};
140use crate::{ConstraintSpace, ContainingBlock, IndefiniteContainingBlock, SharedStyle};
141
142// From gfxFontConstants.h in Firefox.
143static FONT_SUBSCRIPT_OFFSET_RATIO: f32 = 0.20;
144static FONT_SUPERSCRIPT_OFFSET_RATIO: f32 = 0.34;
145
146#[derive(Debug, MallocSizeOf)]
147pub(crate) struct InlineFormattingContext {
148    /// All [`InlineItem`]s in this [`InlineFormattingContext`] stored in a flat array.
149    /// [`InlineItem::StartInlineBox`] and [`InlineItem::EndInlineBox`] allow representing
150    /// the tree of inline boxes within the formatting context, but a flat array allows
151    /// easy iteration through all inline items.
152    inline_items: Vec<InlineItem>,
153
154    /// The tree of inline boxes in this [`InlineFormattingContext`]. These are stored in
155    /// a flat array with each being given a [`InlineBoxIdentifier`].
156    inline_boxes: InlineBoxes,
157
158    /// The text content of this inline formatting context.
159    text_content: String,
160
161    /// The [`SharedInlineStyles`] for the root of this [`InlineFormattingContext`] that are used to
162    /// share styles with all [`TextRun`] children.
163    shared_inline_styles: SharedInlineStyles,
164
165    /// The default font that is used for the root of this [`InlineFormattingContext`]. This is the
166    /// font used when the font fallback code path is not taken. It may be `None` if no default
167    /// font was found (this typically means that no characters can be rendered).
168    default_font: Option<FontRef>,
169
170    /// Whether this IFC contains the 1st formatted line of an element:
171    /// <https://www.w3.org/TR/css-pseudo-4/#first-formatted-line>.
172    has_first_formatted_line: bool,
173
174    /// Whether or not this [`InlineFormattingContext`] contains floats.
175    pub(super) contains_floats: bool,
176
177    /// Whether or not this is an [`InlineFormattingContext`] for a single line text input's inner
178    /// text container.
179    is_single_line_text_input: bool,
180
181    /// Whether or not this is an [`InlineFormattingContext`] has right-to-left content, which
182    /// will require reordering during layout.
183    has_right_to_left_content: bool,
184
185    /// If this [`InlineFormattingContext`] has a selection shared with its originating
186    /// node in the DOM, this will not be `None`.
187    #[ignore_malloc_size_of = "This is stored primarily in the DOM"]
188    shared_selection: Option<SharedSelection>,
189
190    /// The cached tab stop inline advance used when finding final tab stops for preserved tabs.
191    tab_stop_advance: OnceLock<Au>,
192}
193
194/// [`TextRun`] and `TextFragment`s need a handle on their parent inline box (or inline
195/// formatting context root)'s style. In order to implement incremental layout, these are
196/// wrapped in [`SharedStyle`]. This allows updating the parent box tree element without
197/// updating every single descendant box tree node and fragment.
198#[derive(Clone, Debug, MallocSizeOf)]
199pub(crate) struct SharedInlineStyles {
200    pub style: SharedStyle,
201    pub selected: SharedStyle,
202}
203
204impl SharedInlineStyles {
205    pub(crate) fn ptr_eq(&self, other: &Self) -> bool {
206        self.style.ptr_eq(&other.style) && self.selected.ptr_eq(&other.selected)
207    }
208
209    pub(crate) fn from_info_and_context(info: &NodeAndStyleInfo, context: &LayoutContext) -> Self {
210        Self {
211            style: SharedStyle::new(info.style.clone()),
212            selected: SharedStyle::new(info.node.selected_style(&context.style_context)),
213        }
214    }
215}
216
217impl BlockLevelBox {
218    fn layout_into_line_items(&self, layout: &mut InlineFormattingContextLayout) {
219        layout.process_soft_wrap_opportunity();
220        layout.commit_current_segment_to_line();
221        layout.process_line_break(true);
222        layout.current_line.for_block_level = true;
223
224        let fragment = layout_block_level_child(
225            layout.layout_context,
226            layout.positioning_context,
227            self,
228            layout.sequential_layout_state.as_deref_mut(),
229            &mut layout.placement_state,
230            // Under discussion in <https://github.com/w3c/csswg-drafts/issues/13260>.
231            LogicalSides1D::new(false, false),
232            true, /* has_inline_parent */
233        );
234
235        let Fragment::Box(fragment) = fragment else {
236            unreachable!("The fragment should be a Fragment::Box()");
237        };
238
239        // If this Fragment's layout depends on the block size of the containing block,
240        // then the entire layout of the inline formatting context does as well.
241        layout.depends_on_block_constraints |= fragment.base.flags.contains(
242            FragmentFlags::SIZE_DEPENDS_ON_BLOCK_CONSTRAINTS_AND_CAN_BE_CHILD_OF_FLEX_ITEM,
243        );
244
245        layout.push_line_item_to_unbreakable_segment(LineItem::BlockLevel(
246            layout.current_inline_box_identifier(),
247            fragment,
248        ));
249
250        layout.commit_current_segment_to_line();
251        layout.process_line_break(true);
252        layout.current_line.for_block_level = false;
253    }
254}
255
256#[derive(Clone, Debug, MallocSizeOf)]
257pub(crate) enum InlineItem {
258    StartInlineBox(ArcRefCell<InlineBox>),
259    EndInlineBox,
260    TextRun(ArcRefCell<TextRun>),
261    OutOfFlowAbsolutelyPositionedBox(
262        ArcRefCell<AbsolutelyPositionedBox>,
263        usize, /* offset_in_text */
264    ),
265    OutOfFlowFloatBox(ArcRefCell<FloatBox>),
266    Atomic(
267        ArcRefCell<IndependentFormattingContext>,
268        usize, /* offset_in_text */
269        Level, /* bidi_level */
270    ),
271    BlockLevel(ArcRefCell<BlockLevelBox>),
272}
273
274impl InlineItem {
275    pub(crate) fn repair_style(
276        &self,
277        context: &SharedStyleContext,
278        node: &ServoLayoutNode,
279        new_style: &ServoArc<ComputedValues>,
280    ) {
281        match self {
282            InlineItem::StartInlineBox(inline_box) => {
283                inline_box
284                    .borrow_mut()
285                    .repair_style(context, node, new_style);
286            },
287            InlineItem::EndInlineBox => {},
288            // TextRun holds a handle the `InlineSharedStyles` which is updated when repairing inline box
289            // and `display: contents` styles.
290            InlineItem::TextRun(..) => {},
291            InlineItem::OutOfFlowAbsolutelyPositionedBox(positioned_box, ..) => positioned_box
292                .borrow_mut()
293                .context
294                .repair_style(context, node, new_style),
295            InlineItem::OutOfFlowFloatBox(float_box) => float_box
296                .borrow_mut()
297                .contents
298                .repair_style(context, node, new_style),
299            InlineItem::Atomic(atomic, ..) => {
300                atomic.borrow_mut().repair_style(context, node, new_style)
301            },
302            InlineItem::BlockLevel(block_level) => block_level
303                .borrow_mut()
304                .repair_style(context, node, new_style),
305        }
306    }
307
308    pub(crate) fn with_base<T>(&self, callback: impl FnOnce(&LayoutBoxBase) -> T) -> T {
309        match self {
310            InlineItem::StartInlineBox(inline_box) => callback(&inline_box.borrow().base),
311            InlineItem::EndInlineBox | InlineItem::TextRun(..) => {
312                unreachable!("Should never have these kind of fragments attached to a DOM node")
313            },
314            InlineItem::OutOfFlowAbsolutelyPositionedBox(positioned_box, ..) => {
315                callback(&positioned_box.borrow().context.base)
316            },
317            InlineItem::OutOfFlowFloatBox(float_box) => callback(&float_box.borrow().contents.base),
318            InlineItem::Atomic(independent_formatting_context, ..) => {
319                callback(&independent_formatting_context.borrow().base)
320            },
321            InlineItem::BlockLevel(block_level) => block_level.borrow().with_base(callback),
322        }
323    }
324
325    pub(crate) fn with_base_mut<T>(&self, callback: impl FnOnce(&mut LayoutBoxBase) -> T) -> T {
326        match self {
327            InlineItem::StartInlineBox(inline_box) => callback(&mut inline_box.borrow_mut().base),
328            InlineItem::EndInlineBox | InlineItem::TextRun(..) => {
329                unreachable!("Should never have these kind of fragments attached to a DOM node")
330            },
331            InlineItem::OutOfFlowAbsolutelyPositionedBox(positioned_box, ..) => {
332                callback(&mut positioned_box.borrow_mut().context.base)
333            },
334            InlineItem::OutOfFlowFloatBox(float_box) => {
335                callback(&mut float_box.borrow_mut().contents.base)
336            },
337            InlineItem::Atomic(independent_formatting_context, ..) => {
338                callback(&mut independent_formatting_context.borrow_mut().base)
339            },
340            InlineItem::BlockLevel(block_level) => block_level.borrow_mut().with_base_mut(callback),
341        }
342    }
343
344    pub(crate) fn attached_to_tree(&self, layout_box: WeakLayoutBox) {
345        match self {
346            Self::StartInlineBox(_) | InlineItem::EndInlineBox => {
347                // The parentage of inline items within an inline box is handled when the entire
348                // inline formatting context is attached to the tree.
349            },
350            Self::TextRun(_) => {
351                // Text runs can't have children, so no need to do anything.
352            },
353            Self::OutOfFlowAbsolutelyPositionedBox(positioned_box, ..) => {
354                positioned_box.borrow().context.attached_to_tree(layout_box)
355            },
356            Self::OutOfFlowFloatBox(float_box) => {
357                float_box.borrow().contents.attached_to_tree(layout_box)
358            },
359            Self::Atomic(atomic, ..) => atomic.borrow().attached_to_tree(layout_box),
360            Self::BlockLevel(block_level) => block_level.borrow().attached_to_tree(layout_box),
361        }
362    }
363
364    pub(crate) fn downgrade(&self) -> WeakInlineItem {
365        match self {
366            Self::StartInlineBox(inline_box) => {
367                WeakInlineItem::StartInlineBox(inline_box.downgrade())
368            },
369            Self::EndInlineBox => WeakInlineItem::EndInlineBox,
370            Self::TextRun(text_run) => WeakInlineItem::TextRun(text_run.downgrade()),
371            Self::OutOfFlowAbsolutelyPositionedBox(positioned_box, offset_in_text) => {
372                WeakInlineItem::OutOfFlowAbsolutelyPositionedBox(
373                    positioned_box.downgrade(),
374                    *offset_in_text,
375                )
376            },
377            Self::OutOfFlowFloatBox(float_box) => {
378                WeakInlineItem::OutOfFlowFloatBox(float_box.downgrade())
379            },
380            Self::Atomic(atomic, offset_in_text, bidi_level) => {
381                WeakInlineItem::Atomic(atomic.downgrade(), *offset_in_text, *bidi_level)
382            },
383            Self::BlockLevel(block_level) => WeakInlineItem::BlockLevel(block_level.downgrade()),
384        }
385    }
386}
387
388#[derive(Clone, Debug, MallocSizeOf)]
389pub(crate) enum WeakInlineItem {
390    StartInlineBox(WeakRefCell<InlineBox>),
391    EndInlineBox,
392    TextRun(WeakRefCell<TextRun>),
393    OutOfFlowAbsolutelyPositionedBox(
394        WeakRefCell<AbsolutelyPositionedBox>,
395        usize, /* offset_in_text */
396    ),
397    OutOfFlowFloatBox(WeakRefCell<FloatBox>),
398    Atomic(
399        WeakRefCell<IndependentFormattingContext>,
400        usize, /* offset_in_text */
401        Level, /* bidi_level */
402    ),
403    BlockLevel(WeakRefCell<BlockLevelBox>),
404}
405
406impl WeakInlineItem {
407    pub(crate) fn upgrade(&self) -> Option<InlineItem> {
408        Some(match self {
409            Self::StartInlineBox(inline_box) => InlineItem::StartInlineBox(inline_box.upgrade()?),
410            Self::EndInlineBox => InlineItem::EndInlineBox,
411            Self::TextRun(text_run) => InlineItem::TextRun(text_run.upgrade()?),
412            Self::OutOfFlowAbsolutelyPositionedBox(positioned_box, offset_in_text) => {
413                InlineItem::OutOfFlowAbsolutelyPositionedBox(
414                    positioned_box.upgrade()?,
415                    *offset_in_text,
416                )
417            },
418            Self::OutOfFlowFloatBox(float_box) => {
419                InlineItem::OutOfFlowFloatBox(float_box.upgrade()?)
420            },
421            Self::Atomic(atomic, offset_in_text, bidi_level) => {
422                InlineItem::Atomic(atomic.upgrade()?, *offset_in_text, *bidi_level)
423            },
424            Self::BlockLevel(block_level) => InlineItem::BlockLevel(block_level.upgrade()?),
425        })
426    }
427}
428
429/// Information about the current line under construction for a particular
430/// [`InlineFormattingContextLayout`]. This tracks position and size information while
431/// [`LineItem`]s are collected and is used as input when those [`LineItem`]s are
432/// converted into [`Fragment`]s during the final phase of line layout. Note that this
433/// does not store the [`LineItem`]s themselves, as they are stored as part of the
434/// nesting state in the [`InlineFormattingContextLayout`].
435struct LineUnderConstruction {
436    /// The position where this line will start once it is laid out. This includes any
437    /// offset from `text-indent`.
438    start_position: LogicalVec2<Au>,
439
440    /// The current inline position in the line being laid out into [`LineItem`]s in this
441    /// [`InlineFormattingContext`] independent of the depth in the nesting level.
442    inline_position: Au,
443
444    /// The maximum block size of all boxes that ended and are in progress in this line.
445    /// This uses [`LineBlockSizes`] instead of a simple value, because the final block size
446    /// depends on vertical alignment.
447    max_block_size: LineBlockSizes,
448
449    /// Whether any active linebox has added a glyph or atomic element to this line, which
450    /// indicates that the next run that exceeds the line length can cause a line break.
451    has_content: bool,
452
453    /// Whether any active linebox has added some inline-axis padding, border or margin
454    /// to this line.
455    has_inline_pbm: bool,
456
457    /// Whether or not there are floats that did not fit on the current line. Before
458    /// the [`LineItem`]s of this line are laid out, these floats will need to be
459    /// placed directly below this line, but still as children of this line's Fragments.
460    has_floats_waiting_to_be_placed: bool,
461
462    /// A rectangular area (relative to the containing block / inline formatting
463    /// context boundaries) where we can fit the line box without overlapping floats.
464    /// Note that when this is not empty, its start corner takes precedence over
465    /// [`LineUnderConstruction::start_position`].
466    placement_among_floats: OnceCell<LogicalRect<Au>>,
467
468    /// The LineItems for the current line under construction that have already
469    /// been committed to this line.
470    line_items: Vec<LineItem>,
471
472    /// Whether the current line is for a block-level box.
473    for_block_level: bool,
474
475    /// The starting character offset of this line.
476    ///
477    /// This is used to generate empty `TextRunLineItem` to hold text carets on otherwise
478    /// empty lines.
479    ///
480    /// TODO: This is only guaranteed to be accurate for the first line or when the previous line
481    /// ended with a hard line break. Eventually this should be updated during content processing so
482    /// that text carets work outside of text inputs.
483    starting_character_offset: usize,
484}
485
486impl LineUnderConstruction {
487    fn new(start_position: LogicalVec2<Au>) -> Self {
488        Self {
489            inline_position: start_position.inline,
490            start_position,
491            max_block_size: LineBlockSizes::zero(),
492            has_content: false,
493            has_inline_pbm: false,
494            has_floats_waiting_to_be_placed: false,
495            placement_among_floats: OnceCell::new(),
496            line_items: Vec::new(),
497            for_block_level: false,
498            starting_character_offset: 0,
499        }
500    }
501
502    fn replace_placement_among_floats(&mut self, new_placement: LogicalRect<Au>) {
503        self.placement_among_floats.take();
504        let _ = self.placement_among_floats.set(new_placement);
505    }
506
507    /// Trim the trailing whitespace in this line and return the width of the whitespace trimmed.
508    fn trim_trailing_whitespace(&mut self) -> Au {
509        // From <https://www.w3.org/TR/css-text-3/#white-space-phase-2>:
510        // > 3. A sequence of collapsible spaces at the end of a line is removed,
511        // >    as well as any trailing U+1680   OGHAM SPACE MARK whose white-space
512        // >    property is normal, nowrap, or pre-line.
513        let mut whitespace_trimmed = Au::zero();
514        for item in self.line_items.iter_mut().rev() {
515            if !item.trim_whitespace_at_end(&mut whitespace_trimmed) {
516                break;
517            }
518        }
519
520        whitespace_trimmed
521    }
522
523    /// Count the number of justification opportunities in this line.
524    fn count_justification_opportunities(&self) -> usize {
525        self.line_items
526            .iter()
527            .filter_map(|item| match item {
528                LineItem::TextRun(_, text_run) => Some(
529                    text_run
530                        .text
531                        .iter()
532                        .map(|shaped_text_slice| shaped_text_slice.total_word_separators())
533                        .sum::<usize>(),
534                ),
535                _ => None,
536            })
537            .sum()
538    }
539
540    /// Whether this is a phantom line box.
541    /// <https://drafts.csswg.org/css-inline-3/#invisible-line-boxes>
542    fn is_phantom(&self) -> bool {
543        // Keep this logic in sync with `UnbreakableSegmentUnderConstruction::is_phantom()`.
544        !self.has_content && !self.has_inline_pbm
545    }
546}
547
548/// A block size relative to a line's final baseline. This is to track the size
549/// contribution of a particular element of a line above and below the baseline.
550/// These sizes can be combined with other baseline relative sizes before the
551/// final baseline position is known. The values here are relative to the
552/// overall line's baseline and *not* the nested baseline of an inline box.
553#[derive(Clone, Debug)]
554struct BaselineRelativeSize {
555    /// The ascent above the baseline, where a positive value means a larger
556    /// ascent. Thus, the top of this size contribution is `baseline_offset -
557    /// ascent`.
558    ascent: Au,
559
560    /// The descent below the baseline, where a positive value means a larger
561    /// descent. Thus, the bottom of this size contribution is `baseline_offset +
562    /// descent`.
563    descent: Au,
564}
565
566impl BaselineRelativeSize {
567    fn zero() -> Self {
568        Self {
569            ascent: Au::zero(),
570            descent: Au::zero(),
571        }
572    }
573
574    fn max(&self, other: &Self) -> Self {
575        BaselineRelativeSize {
576            ascent: self.ascent.max(other.ascent),
577            descent: self.descent.max(other.descent),
578        }
579    }
580
581    /// Given an offset from the line's root baseline, adjust this [`BaselineRelativeSize`]
582    /// by that offset. This is used to adjust a [`BaselineRelativeSize`] for different kinds
583    /// of baseline-relative `vertical-align`. This will "move" measured size of a particular
584    /// inline box's block size. For example, in the following HTML:
585    ///
586    /// ```html
587    ///     <div>
588    ///         <span style="vertical-align: 5px">child content</span>
589    ///     </div>
590    /// ````
591    ///
592    /// If this [`BaselineRelativeSize`] is for the `<span>` then the adjustment
593    /// passed here would be equivalent to -5px.
594    fn adjust_for_nested_baseline_offset(&mut self, baseline_offset: Au) {
595        self.ascent -= baseline_offset;
596        self.descent += baseline_offset;
597    }
598}
599
600#[derive(Clone, Debug)]
601struct LineBlockSizes {
602    line_height: Au,
603    baseline_relative_size_for_line_height: Option<BaselineRelativeSize>,
604    size_for_baseline_positioning: BaselineRelativeSize,
605}
606
607impl LineBlockSizes {
608    fn zero() -> Self {
609        LineBlockSizes {
610            line_height: Au::zero(),
611            baseline_relative_size_for_line_height: None,
612            size_for_baseline_positioning: BaselineRelativeSize::zero(),
613        }
614    }
615
616    fn resolve(&self) -> Au {
617        let height_from_ascent_and_descent = self
618            .baseline_relative_size_for_line_height
619            .as_ref()
620            .map(|size| (size.ascent + size.descent).abs())
621            .unwrap_or_else(Au::zero);
622        self.line_height.max(height_from_ascent_and_descent)
623    }
624
625    fn max(&self, other: &LineBlockSizes) -> LineBlockSizes {
626        let baseline_relative_size = match (
627            self.baseline_relative_size_for_line_height.as_ref(),
628            other.baseline_relative_size_for_line_height.as_ref(),
629        ) {
630            (Some(our_size), Some(other_size)) => Some(our_size.max(other_size)),
631            (our_size, other_size) => our_size.or(other_size).cloned(),
632        };
633        Self {
634            line_height: self.line_height.max(other.line_height),
635            baseline_relative_size_for_line_height: baseline_relative_size,
636            size_for_baseline_positioning: self
637                .size_for_baseline_positioning
638                .max(&other.size_for_baseline_positioning),
639        }
640    }
641
642    fn max_assign(&mut self, other: &LineBlockSizes) {
643        *self = self.max(other);
644    }
645
646    fn adjust_for_baseline_offset(&mut self, baseline_offset: Au) {
647        if let Some(size) = self.baseline_relative_size_for_line_height.as_mut() {
648            size.adjust_for_nested_baseline_offset(baseline_offset)
649        }
650        self.size_for_baseline_positioning
651            .adjust_for_nested_baseline_offset(baseline_offset);
652    }
653
654    /// From <https://drafts.csswg.org/css2/visudet.html#line-height>:
655    ///  > The inline-level boxes are aligned vertically according to their 'vertical-align'
656    ///  > property. In case they are aligned 'top' or 'bottom', they must be aligned so as
657    ///  > to minimize the line box height. If such boxes are tall enough, there are multiple
658    ///  > solutions and CSS 2 does not define the position of the line box's baseline (i.e.,
659    ///  > the position of the strut, see below).
660    fn find_baseline_offset(&self) -> Au {
661        match self.baseline_relative_size_for_line_height.as_ref() {
662            Some(size) => size.ascent,
663            None => {
664                // This is the case mentinoned above where there are multiple solutions.
665                // This code is putting the baseline roughly in the middle of the line.
666                let leading = self.resolve() -
667                    (self.size_for_baseline_positioning.ascent +
668                        self.size_for_baseline_positioning.descent);
669                leading.scale_by(0.5) + self.size_for_baseline_positioning.ascent
670            },
671        }
672    }
673}
674
675/// The current unbreakable segment under construction for an inline formatting context.
676/// Items accumulate here until we reach a soft line break opportunity during processing
677/// of inline content or we reach the end of the formatting context.
678struct UnbreakableSegmentUnderConstruction {
679    /// The size of this unbreakable segment in both dimension.
680    inline_size: Au,
681
682    /// The maximum block size that this segment has. This uses [`LineBlockSizes`] instead of a
683    /// simple value, because the final block size depends on vertical alignment.
684    max_block_size: LineBlockSizes,
685
686    /// The LineItems for the segment under construction
687    line_items: Vec<LineItem>,
688
689    /// The depth in the inline box hierarchy at the start of this segment. This is used
690    /// to prefix this segment when it is pushed to a new line.
691    inline_box_hierarchy_depth: Option<usize>,
692
693    /// Whether any active linebox has added a glyph or atomic element to this line
694    /// segment, which indicates that the next run that exceeds the line length can cause
695    /// a line break.
696    has_content: bool,
697
698    /// Whether any active linebox has added some inline-axis padding, border or margin
699    /// to this line segment.
700    has_inline_pbm: bool,
701
702    /// The inline size of any trailing whitespace in this segment.
703    trailing_whitespace_size: Au,
704}
705
706impl UnbreakableSegmentUnderConstruction {
707    fn new() -> Self {
708        Self {
709            inline_size: Au::zero(),
710            max_block_size: LineBlockSizes {
711                line_height: Au::zero(),
712                baseline_relative_size_for_line_height: None,
713                size_for_baseline_positioning: BaselineRelativeSize::zero(),
714            },
715            line_items: Vec::new(),
716            inline_box_hierarchy_depth: None,
717            has_content: false,
718            has_inline_pbm: false,
719            trailing_whitespace_size: Au::zero(),
720        }
721    }
722
723    /// Reset this segment after its contents have been committed to a line.
724    fn reset(&mut self) {
725        assert!(self.line_items.is_empty()); // Preserve allocated memory.
726        self.inline_size = Au::zero();
727        self.max_block_size = LineBlockSizes::zero();
728        self.inline_box_hierarchy_depth = None;
729        self.has_content = false;
730        self.has_inline_pbm = false;
731        self.trailing_whitespace_size = Au::zero();
732    }
733
734    /// Push a single line item to this segment. In addition, record the inline box
735    /// hierarchy depth if this is the first segment. The hierarchy depth is used to
736    /// duplicate the necessary `StartInlineBox` tokens if this segment is ultimately
737    /// placed on a new empty line.
738    fn push_line_item(&mut self, line_item: LineItem, inline_box_hierarchy_depth: usize) {
739        if self.line_items.is_empty() {
740            self.inline_box_hierarchy_depth = Some(inline_box_hierarchy_depth);
741        }
742        self.line_items.push(line_item);
743    }
744
745    /// Trim whitespace from the beginning of this UnbreakbleSegmentUnderConstruction.
746    ///
747    /// From <https://www.w3.org/TR/css-text-3/#white-space-phase-2>:
748    ///
749    /// > Then, the entire block is rendered. Inlines are laid out, taking bidi
750    /// > reordering into account, and wrapping as specified by the text-wrap
751    /// > property. As each line is laid out,
752    /// >  1. A sequence of collapsible spaces at the beginning of a line is removed.
753    ///
754    /// This prevents whitespace from being added to the beginning of a line.
755    fn trim_leading_whitespace(&mut self) {
756        let mut whitespace_trimmed = Au::zero();
757        for item in self.line_items.iter_mut() {
758            if !item.trim_whitespace_at_start(&mut whitespace_trimmed) {
759                break;
760            }
761        }
762        self.inline_size -= whitespace_trimmed;
763    }
764
765    /// Whether this is segment is phantom. If false, its line box won't be phantom.
766    /// <https://drafts.csswg.org/css-inline-3/#invisible-line-boxes>
767    fn is_phantom(&self) -> bool {
768        // Keep this logic in sync with `LineUnderConstruction::is_phantom()`.
769        !self.has_content && !self.has_inline_pbm
770    }
771}
772
773bitflags! {
774    struct InlineContainerStateFlags: u8 {
775        const CREATE_STRUT = 0b0001;
776        const IS_SINGLE_LINE_TEXT_INPUT = 0b0010;
777    }
778}
779
780struct InlineContainerState {
781    /// The style of this inline container.
782    style: ServoArc<ComputedValues>,
783
784    /// Flags which describe details of this [`InlineContainerState`].
785    flags: InlineContainerStateFlags,
786
787    /// Whether or not we have processed any content (an atomic element or text) for
788    /// this inline box on the current line OR any previous line.
789    has_content: RefCell<bool>,
790
791    /// The block size contribution of this container's default font ie the size of the
792    /// "strut." Whether this is integrated into the [`Self::nested_strut_block_sizes`]
793    /// depends on the line-height quirk described in
794    /// <https://quirks.spec.whatwg.org/#the-line-height-calculation-quirk>.
795    strut_block_sizes: LineBlockSizes,
796
797    /// The strut block size of this inline container maxed with the strut block
798    /// sizes of all inline container ancestors. In quirks mode, this will be
799    /// zero, until we know that an element has inline content.
800    nested_strut_block_sizes: LineBlockSizes,
801
802    /// The baseline offset of this container from the baseline of the line. The is the
803    /// cumulative offset of this container and all of its parents. In contrast to the
804    /// `vertical-align` property a positive value indicates an offset "below" the
805    /// baseline while a negative value indicates one "above" it (when the block direction
806    /// is vertical).
807    pub baseline_offset: Au,
808
809    /// The primary font used for this container, if one exists. This is the font that is
810    /// used when not falling back.
811    default_font: Option<FontRef>,
812
813    /// The font metrics of the non-fallback font for this container.
814    font_metrics: Arc<FontMetrics>,
815}
816
817struct InlineFormattingContextLayout<'layout_data> {
818    positioning_context: &'layout_data mut PositioningContext,
819    placement_state: PlacementState<'layout_data>,
820    sequential_layout_state: Option<&'layout_data mut SequentialLayoutState>,
821    layout_context: &'layout_data LayoutContext<'layout_data>,
822
823    /// The [`InlineFormattingContext`] that we are laying out.
824    ifc: &'layout_data InlineFormattingContext,
825
826    /// The [`InlineContainerState`] for the container formed by the root of the
827    /// [`InlineFormattingContext`]. This is effectively the "root inline box" described
828    /// by <https://drafts.csswg.org/css-inline/#model>:
829    ///
830    /// > The block container also generates a root inline box, which is an anonymous
831    /// > inline box that holds all of its inline-level contents. (Thus, all text in an
832    /// > inline formatting context is directly contained by an inline box, whether the root
833    /// > inline box or one of its descendants.) The root inline box inherits from its
834    /// > parent block container, but is otherwise unstyleable.
835    root_nesting_level: InlineContainerState,
836
837    /// A stack of [`InlineBoxContainerState`] that is used to produce [`LineItem`]s either when we
838    /// reach the end of an inline box or when we reach the end of a line. Only at the end
839    /// of the inline box is the state popped from the stack.
840    inline_box_state_stack: Vec<Rc<InlineBoxContainerState>>,
841
842    /// A collection of [`InlineBoxContainerState`] of all the inlines that are present
843    /// in this inline formatting context. We keep this as well as the stack, so that we
844    /// can access them during line layout, which may happen after relevant [`InlineBoxContainerState`]s
845    /// have been popped of the stack.
846    inline_box_states: Vec<Rc<InlineBoxContainerState>>,
847
848    /// A vector of fragment that are laid out. This includes one [`Fragment::Positioning`]
849    /// per line that is currently laid out plus fragments for all floats, which
850    /// are currently laid out at the top-level of each [`InlineFormattingContext`].
851    fragments: Vec<Fragment>,
852
853    /// Information about the line currently being laid out into [`LineItem`]s.
854    current_line: LineUnderConstruction,
855
856    /// Information about the unbreakable line segment currently being laid out into [`LineItem`]s.
857    current_line_segment: UnbreakableSegmentUnderConstruction,
858
859    /// After a forced line break (for instance from a `<br>` element) we wait to actually
860    /// break the line until seeing more content. This allows ongoing inline boxes to finish,
861    /// since in the case where they have no more content they should not be on the next
862    /// line.
863    ///
864    /// For instance:
865    ///
866    /// ``` html
867    ///    <span style="border-right: 30px solid blue;">
868    ///         first line<br>
869    ///    </span>
870    ///    second line
871    /// ```
872    ///
873    /// In this case, the `<span>` should not extend to the second line. If we linebreak
874    /// as soon as we encounter the `<br>` the `<span>`'s ending inline borders would be
875    /// placed on the second line, because we add those borders in
876    /// [`InlineFormattingContextLayout::finish_inline_box()`].
877    ///
878    /// If this field is `Some`, a hard line break should be processed before any new content. The
879    /// `usize` stores the character offset of the originating hard line break, which is used to
880    /// generate placeholders for carets on otherwise empty lines.
881    force_line_break_before_new_content: Option<usize>,
882
883    /// When a `<br>` element has `clear`, this needs to be applied after the linebreak,
884    /// which will be processed *after* the `<br>` element is processed. This member
885    /// stores any deferred `clear` to apply after a linebreak.
886    deferred_br_clear: Clear,
887
888    /// Whether or not a soft wrap opportunity is queued. Soft wrap opportunities are
889    /// queued after replaced content and they are processed when the next text content
890    /// is encountered.
891    pub have_deferred_soft_wrap_opportunity: bool,
892
893    /// Whether or not the layout of this InlineFormattingContext depends on the block size
894    /// of its container for the purposes of flexbox layout.
895    depends_on_block_constraints: bool,
896
897    /// The currently white-space-collapse setting of this line. This is stored on the
898    /// [`InlineFormattingContextLayout`] because when a soft wrap opportunity is defined
899    /// by the boundary between two characters, the white-space-collapse property of their
900    /// nearest common ancestor is used.
901    white_space_collapse: WhiteSpaceCollapse,
902
903    /// The currently text-wrap-mode setting of this line. This is stored on the
904    /// [`InlineFormattingContextLayout`] because when a soft wrap opportunity is defined
905    /// by the boundary between two characters, the text-wrap-mode property of their nearest
906    /// common ancestor is used.
907    text_wrap_mode: TextWrapMode,
908}
909
910impl InlineFormattingContextLayout<'_> {
911    fn current_inline_container_state(&self) -> &InlineContainerState {
912        match self.inline_box_state_stack.last() {
913            Some(inline_box_state) => &inline_box_state.base,
914            None => &self.root_nesting_level,
915        }
916    }
917
918    fn current_inline_box_identifier(&self) -> Option<InlineBoxIdentifier> {
919        self.inline_box_state_stack
920            .last()
921            .map(|state| state.identifier)
922    }
923
924    fn current_line_max_block_size_including_nested_containers(&self) -> LineBlockSizes {
925        self.current_inline_container_state()
926            .nested_strut_block_sizes
927            .max(&self.current_line.max_block_size)
928    }
929
930    fn current_line_block_start_considering_placement_among_floats(&self) -> Au {
931        self.current_line.placement_among_floats.get().map_or(
932            self.current_line.start_position.block,
933            |placement_among_floats| placement_among_floats.start_corner.block,
934        )
935    }
936
937    fn propagate_current_nesting_level_white_space_style(&mut self) {
938        let style = match self.inline_box_state_stack.last() {
939            Some(inline_box_state) => &inline_box_state.base.style,
940            None => self.placement_state.containing_block.style,
941        };
942        let style_text = style.get_inherited_text();
943        self.white_space_collapse = style_text.white_space_collapse;
944        self.text_wrap_mode = style_text.text_wrap_mode;
945    }
946
947    fn processing_br_element(&self) -> bool {
948        self.inline_box_state_stack.last().is_some_and(|state| {
949            state
950                .base_fragment_info
951                .flags
952                .contains(FragmentFlags::IS_BR_ELEMENT)
953        })
954    }
955
956    /// Start laying out a particular [`InlineBox`] into line items. This will push
957    /// a new [`InlineBoxContainerState`] onto [`Self::inline_box_state_stack`].
958    fn start_inline_box(&mut self, inline_box: &InlineBox) {
959        let containing_block = self.containing_block();
960        let inline_box_state = InlineBoxContainerState::new(
961            inline_box,
962            containing_block,
963            self.layout_context,
964            self.current_inline_container_state(),
965            inline_box.default_font.clone(),
966        );
967
968        self.depends_on_block_constraints |= inline_box
969            .base
970            .style
971            .depends_on_block_constraints_due_to_relative_positioning(
972                containing_block.style.writing_mode,
973            );
974
975        // If we are starting a `<br>` element prepare to clear after its deferred linebreak has been
976        // processed. Note that a `<br>` is composed of the element itself and the inner pseudo-element
977        // with the actual linebreak. Both will have this `FragmentFlag`; that's why this code only
978        // sets `deferred_br_clear` if it isn't set yet.
979        if inline_box_state
980            .base_fragment_info
981            .flags
982            .contains(FragmentFlags::IS_BR_ELEMENT) &&
983            self.deferred_br_clear == Clear::None
984        {
985            self.deferred_br_clear = Clear::from_style_and_container_writing_mode(
986                &inline_box_state.base.style,
987                self.containing_block().style.writing_mode,
988            );
989        }
990
991        let padding = inline_box_state.pbm.padding.inline_start;
992        let border = inline_box_state.pbm.border.inline_start;
993        let margin = inline_box_state.pbm.margin.inline_start.auto_is(Au::zero);
994        // We can't just check if the sum is zero because the margin can be negative,
995        // we need to check the values separately.
996        if !padding.is_zero() || !border.is_zero() || !margin.is_zero() {
997            self.current_line_segment.has_inline_pbm = true;
998        }
999        self.current_line_segment.inline_size += padding + border + margin;
1000        self.current_line_segment
1001            .line_items
1002            .push(LineItem::InlineStartBoxPaddingBorderMargin(
1003                inline_box.identifier,
1004            ));
1005
1006        let inline_box_state = Rc::new(inline_box_state);
1007
1008        // Push the state onto the IFC-wide collection of states. Inline boxes are numbered in
1009        // the order that they are encountered, so this should correspond to the order they
1010        // are pushed onto `self.inline_box_states`.
1011        assert_eq!(
1012            self.inline_box_states.len(),
1013            inline_box.identifier.index_in_inline_boxes as usize
1014        );
1015        self.inline_box_states.push(inline_box_state.clone());
1016        self.inline_box_state_stack.push(inline_box_state);
1017    }
1018
1019    /// Finish laying out a particular [`InlineBox`] into line items. This will
1020    /// pop its state off of [`Self::inline_box_state_stack`].
1021    fn finish_inline_box(&mut self) {
1022        let inline_box_state = match self.inline_box_state_stack.pop() {
1023            Some(inline_box_state) => inline_box_state,
1024            None => return, // We are at the root.
1025        };
1026
1027        self.current_line_segment
1028            .max_block_size
1029            .max_assign(&inline_box_state.base.nested_strut_block_sizes);
1030
1031        // If the inline box that we just finished had any content at all, we want to propagate
1032        // the `white-space` property of its parent to future inline children. This is because
1033        // when a soft wrap opportunity is defined by the boundary between two elements, the
1034        // `white-space` used is that of their nearest common ancestor.
1035        if *inline_box_state.base.has_content.borrow() {
1036            self.propagate_current_nesting_level_white_space_style();
1037        }
1038
1039        let padding = inline_box_state.pbm.padding.inline_end;
1040        let border = inline_box_state.pbm.border.inline_end;
1041        let margin = inline_box_state.pbm.margin.inline_end.auto_is(Au::zero);
1042        // We can't just check if the sum is zero because the margin can be negative,
1043        // we need to check the values separately.
1044        if !padding.is_zero() || !border.is_zero() || !margin.is_zero() {
1045            self.current_line_segment.has_inline_pbm = true;
1046        }
1047        self.current_line_segment.inline_size += padding + border + margin;
1048        self.current_line_segment
1049            .line_items
1050            .push(LineItem::InlineEndBoxPaddingBorderMargin(
1051                inline_box_state.identifier,
1052            ))
1053    }
1054
1055    fn finish_last_line(&mut self) {
1056        // First, process any deferred forced line breaks.
1057        self.possibly_flush_deferred_forced_line_break();
1058
1059        // We are at the end of the IFC, and we need to do a few things to make sure that
1060        // the current segment is committed and that the final line is finished.
1061        //
1062        // A soft wrap opportunity makes it so the current segment is placed on a new line
1063        // if it doesn't fit on the current line under construction.
1064        self.process_soft_wrap_opportunity();
1065
1066        // `process_soft_line_wrap_opportunity` does not commit the segment to a line if
1067        // there is no line wrapping, so this forces the segment into the current line.
1068        self.commit_current_segment_to_line();
1069
1070        // Finally we finish the line itself and convert all of the LineItems into
1071        // fragments.
1072        self.finish_current_line_and_reset(true /* last_line_or_forced_line_break */);
1073    }
1074
1075    /// Finish layout of all inline boxes for the current line. This will gather all
1076    /// [`LineItem`]s and turn them into [`Fragment`]s, then reset the
1077    /// [`InlineFormattingContextLayout`] preparing it for laying out a new line.
1078    fn finish_current_line_and_reset(&mut self, last_line_or_forced_line_break: bool) {
1079        self.possibly_push_empty_text_run_to_line_for_text_caret();
1080
1081        let whitespace_trimmed = self.current_line.trim_trailing_whitespace();
1082        let (inline_start_position, justification_adjustment) = self
1083            .calculate_current_line_inline_start_and_justification_adjustment(
1084                whitespace_trimmed,
1085                last_line_or_forced_line_break,
1086            );
1087
1088        // https://drafts.csswg.org/css-inline-3/#invisible-line-boxes
1089        // > Line boxes that contain no text, no preserved white space, no inline boxes with non-zero
1090        // > inline-axis margins, padding, or borders, and no other in-flow content (such as atomic
1091        // > inlines or ruby annotations), and do not end with a forced line break are phantom line boxes.
1092        // > Such boxes must be treated as zero-height line boxes for the purposes of determining the
1093        // > positions of any descendant content (such as absolutely positioned boxes), and both the
1094        // > line box and its in-flow content must be treated as not existing for any other layout or
1095        // > rendering purpose.
1096        let is_phantom_line = self.current_line.is_phantom();
1097        if !is_phantom_line {
1098            self.current_line.start_position.block += self.placement_state.current_margin.solve();
1099            self.placement_state.current_margin = CollapsedMargin::zero();
1100        }
1101        let block_start_position =
1102            self.current_line_block_start_considering_placement_among_floats();
1103
1104        let effective_block_advance = if is_phantom_line {
1105            LineBlockSizes::zero()
1106        } else {
1107            self.current_line_max_block_size_including_nested_containers()
1108        };
1109
1110        let resolved_block_advance = effective_block_advance.resolve();
1111        let block_end_position = if self.current_line.for_block_level {
1112            self.placement_state.current_block_direction_position
1113        } else {
1114            let mut block_end_position = block_start_position + resolved_block_advance;
1115            if let Some(sequential_layout_state) = self.sequential_layout_state.as_mut() {
1116                if !is_phantom_line {
1117                    sequential_layout_state.commit_margin();
1118                }
1119
1120                // This amount includes both the block size of the line and any extra space
1121                // added to move the line down in order to avoid overlapping floats.
1122                let increment = block_end_position - self.current_line.start_position.block;
1123                sequential_layout_state.advance_block_position(increment);
1124
1125                // This newline may have been triggered by a `<br>` with clearance, in which case we
1126                // want to make sure that we make space not only for the current line, but any clearance
1127                // from floats.
1128                if let Some(clearance) = sequential_layout_state
1129                    .calculate_clearance(self.deferred_br_clear, &CollapsedMargin::zero())
1130                {
1131                    sequential_layout_state.advance_block_position(clearance);
1132                    block_end_position += clearance;
1133                };
1134                self.deferred_br_clear = Clear::None;
1135            }
1136            block_end_position
1137        };
1138
1139        // Set up the new line now that we no longer need the old one.
1140        let line_to_layout = std::mem::replace(
1141            &mut self.current_line,
1142            LineUnderConstruction::new(LogicalVec2 {
1143                inline: Au::zero(),
1144                block: block_end_position,
1145            }),
1146        );
1147        if !line_to_layout.for_block_level {
1148            self.placement_state.current_block_direction_position = block_end_position;
1149        }
1150
1151        if line_to_layout.has_floats_waiting_to_be_placed {
1152            place_pending_floats(self, &line_to_layout.line_items);
1153        }
1154
1155        let start_position = LogicalVec2 {
1156            block: block_start_position,
1157            inline: inline_start_position,
1158        };
1159
1160        let baseline_offset = effective_block_advance.find_baseline_offset();
1161        let start_positioning_context_length = self.positioning_context.len();
1162        let fragments = LineItemLayout::layout_line_items(
1163            self,
1164            line_to_layout.line_items,
1165            start_position,
1166            &effective_block_advance,
1167            justification_adjustment,
1168            is_phantom_line,
1169        );
1170
1171        if !is_phantom_line {
1172            let baseline = baseline_offset + block_start_position;
1173            self.placement_state
1174                .inflow_baselines
1175                .first
1176                .get_or_insert(baseline);
1177            self.placement_state.inflow_baselines.last = Some(baseline);
1178            self.placement_state
1179                .next_in_flow_margin_collapses_with_parent_start_margin = false;
1180        }
1181
1182        // If the line doesn't have any fragments, we don't need to add a containing fragment for it.
1183        if fragments.is_empty() &&
1184            self.positioning_context.len() == start_positioning_context_length
1185        {
1186            return;
1187        }
1188
1189        // The inline part of this start offset was taken into account when determining
1190        // the inline start of the line in `calculate_inline_start_for_current_line` so
1191        // we do not need to include it in the `start_corner` of the line's main Fragment.
1192        let start_corner = LogicalVec2 {
1193            inline: Au::zero(),
1194            block: block_start_position,
1195        };
1196
1197        let logical_origin_in_physical_coordinates =
1198            start_corner.to_physical_vector(self.containing_block().style.writing_mode);
1199        self.positioning_context
1200            .adjust_static_position_of_hoisted_fragments_with_offset(
1201                &logical_origin_in_physical_coordinates,
1202                start_positioning_context_length,
1203            );
1204
1205        let containing_block = self.containing_block();
1206        let physical_line_rect = LogicalRect {
1207            start_corner,
1208            size: LogicalVec2 {
1209                inline: containing_block.size.inline,
1210                block: effective_block_advance.resolve(),
1211            },
1212        }
1213        .as_physical(Some(containing_block));
1214        self.fragments
1215            .push(Fragment::Positioning(PositioningFragment::new_anonymous(
1216                self.root_nesting_level.style.clone(),
1217                physical_line_rect,
1218                fragments,
1219            )));
1220    }
1221
1222    /// Given the amount of whitespace trimmed from the line and taking into consideration
1223    /// the `text-align` property, calculate where the line under construction starts in
1224    /// the inline axis as well as the adjustment needed for every justification opportunity
1225    /// to account for `text-align: justify`.
1226    fn calculate_current_line_inline_start_and_justification_adjustment(
1227        &self,
1228        whitespace_trimmed: Au,
1229        last_line_or_forced_line_break: bool,
1230    ) -> (Au, Au) {
1231        enum TextAlign {
1232            Start,
1233            Center,
1234            End,
1235        }
1236        let containing_block = self.containing_block();
1237        let style = containing_block.style;
1238        let mut text_align_keyword = style.clone_text_align();
1239
1240        if last_line_or_forced_line_break {
1241            text_align_keyword = match style.clone_text_align_last() {
1242                TextAlignLast::Auto if text_align_keyword == TextAlignKeyword::Justify => {
1243                    TextAlignKeyword::Start
1244                },
1245                TextAlignLast::Auto => text_align_keyword,
1246                TextAlignLast::Start => TextAlignKeyword::Start,
1247                TextAlignLast::End => TextAlignKeyword::End,
1248                TextAlignLast::Left => TextAlignKeyword::Left,
1249                TextAlignLast::Right => TextAlignKeyword::Right,
1250                TextAlignLast::Center => TextAlignKeyword::Center,
1251                TextAlignLast::Justify => TextAlignKeyword::Justify,
1252            };
1253        }
1254
1255        let text_align = match text_align_keyword {
1256            TextAlignKeyword::Start => TextAlign::Start,
1257            TextAlignKeyword::Center | TextAlignKeyword::MozCenter => TextAlign::Center,
1258            TextAlignKeyword::End => TextAlign::End,
1259            TextAlignKeyword::Left | TextAlignKeyword::MozLeft => {
1260                if style.writing_mode.line_left_is_inline_start() {
1261                    TextAlign::Start
1262                } else {
1263                    TextAlign::End
1264                }
1265            },
1266            TextAlignKeyword::Right | TextAlignKeyword::MozRight => {
1267                if style.writing_mode.line_left_is_inline_start() {
1268                    TextAlign::End
1269                } else {
1270                    TextAlign::Start
1271                }
1272            },
1273            TextAlignKeyword::Justify => TextAlign::Start,
1274        };
1275
1276        let (line_start, available_space) = match self.current_line.placement_among_floats.get() {
1277            Some(placement_among_floats) => (
1278                placement_among_floats.start_corner.inline,
1279                placement_among_floats.size.inline,
1280            ),
1281            None => (Au::zero(), containing_block.size.inline),
1282        };
1283
1284        // Properly handling text-indent requires that we do not align the text
1285        // into the text-indent.
1286        // See <https://drafts.csswg.org/css-text/#text-indent-property>
1287        // "This property specifies the indentation applied to lines of inline content in
1288        // a block. The indent is treated as a margin applied to the start edge of the
1289        // line box."
1290        let text_indent = self.current_line.start_position.inline;
1291        let line_length = self.current_line.inline_position - whitespace_trimmed - text_indent;
1292        let adjusted_line_start = line_start +
1293            match text_align {
1294                TextAlign::Start => text_indent,
1295                TextAlign::End => (available_space - line_length).max(text_indent),
1296                TextAlign::Center => (available_space - line_length + text_indent)
1297                    .scale_by(0.5)
1298                    .max(text_indent),
1299            };
1300
1301        // Calculate the justification adjustment. This is simply the remaining space on the line,
1302        // dividided by the number of justficiation opportunities that we recorded when building
1303        // the line.
1304        let text_justify = containing_block.style.clone_text_justify();
1305        let justification_adjustment = match (text_align_keyword, text_justify) {
1306            // `text-justify: none` should disable text justification.
1307            // TODO: Handle more `text-justify` values.
1308            (TextAlignKeyword::Justify, TextJustify::None) => Au::zero(),
1309            (TextAlignKeyword::Justify, _) => {
1310                match self.current_line.count_justification_opportunities() {
1311                    0 => Au::zero(),
1312                    num_justification_opportunities => {
1313                        (available_space - text_indent - line_length)
1314                            .scale_by(1. / num_justification_opportunities as f32)
1315                    },
1316                }
1317            },
1318            _ => Au::zero(),
1319        };
1320
1321        // If the content overflows the line, then justification adjustment will become negative. In
1322        // that case, do not make any adjustment for justification.
1323        let justification_adjustment = justification_adjustment.max(Au::zero());
1324
1325        (adjusted_line_start, justification_adjustment)
1326    }
1327
1328    fn place_float_fragment(&mut self, fragment: &BoxFragment) {
1329        let state = self
1330            .sequential_layout_state
1331            .as_mut()
1332            .expect("Tried to lay out a float with no sequential placement state!");
1333
1334        let block_offset_from_containining_block_top = state
1335            .current_block_position_including_margins() -
1336            state.current_containing_block_offset();
1337        state.place_float_fragment(
1338            fragment,
1339            self.placement_state.containing_block,
1340            CollapsedMargin::zero(),
1341            block_offset_from_containining_block_top,
1342        );
1343    }
1344
1345    /// Place a FloatLineItem. This is done when an unbreakable segment is committed to
1346    /// the current line. Placement of FloatLineItems might need to be deferred until the
1347    /// line is complete in the case that floats stop fitting on the current line.
1348    ///
1349    /// When placing floats we do not want to take into account any trailing whitespace on
1350    /// the line, because that whitespace will be trimmed in the case that the line is
1351    /// broken. Thus this function takes as an argument the new size (without whitespace) of
1352    /// the line that these floats are joining.
1353    fn place_float_line_item_for_commit_to_line(
1354        &mut self,
1355        float_item: &mut FloatLineItem,
1356        line_inline_size_without_trailing_whitespace: Au,
1357    ) {
1358        let containing_block = self.containing_block();
1359        let float_fragment = &float_item.fragment;
1360        let logical_margin_rect_size = float_fragment
1361            .margin_rect()
1362            .size
1363            .to_logical(containing_block.style.writing_mode);
1364        let inline_size = logical_margin_rect_size.inline.max(Au::zero());
1365
1366        let available_inline_size = match self.current_line.placement_among_floats.get() {
1367            Some(placement_among_floats) => placement_among_floats.size.inline,
1368            None => containing_block.size.inline,
1369        } - line_inline_size_without_trailing_whitespace;
1370
1371        // If this float doesn't fit on the current line or a previous float didn't fit on
1372        // the current line, we need to place it starting at the next line BUT still as
1373        // children of this line's hierarchy of inline boxes (for the purposes of properly
1374        // parenting in their stacking contexts). Once all the line content is gathered we
1375        // will place them later.
1376        let has_content = self.current_line.has_content || self.current_line_segment.has_content;
1377        let fits_on_line = !has_content || inline_size <= available_inline_size;
1378        let needs_placement_later =
1379            self.current_line.has_floats_waiting_to_be_placed || !fits_on_line;
1380
1381        if needs_placement_later {
1382            self.current_line.has_floats_waiting_to_be_placed = true;
1383        } else {
1384            self.place_float_fragment(float_fragment);
1385            float_item.needs_placement = false;
1386        }
1387
1388        // We've added a new float to the IFC, but this may have actually changed the
1389        // position of the current line. In order to determine that we regenerate the
1390        // placement among floats for the current line, which may adjust its inline
1391        // start position.
1392        let new_placement = self.place_line_among_floats(&LogicalVec2 {
1393            inline: line_inline_size_without_trailing_whitespace,
1394            block: self.current_line.max_block_size.resolve(),
1395        });
1396        self.current_line
1397            .replace_placement_among_floats(new_placement);
1398    }
1399
1400    /// Given a new potential line size for the current line, create a "placement" for that line.
1401    /// This tells us whether or not the new potential line will fit in the current block position
1402    /// or need to be moved. In addition, the placement rect determines the inline start and end
1403    /// of the line if it's used as the final placement among floats.
1404    fn place_line_among_floats(&self, potential_line_size: &LogicalVec2<Au>) -> LogicalRect<Au> {
1405        let sequential_layout_state = self
1406            .sequential_layout_state
1407            .as_ref()
1408            .expect("Should not have called this function without having floats.");
1409
1410        let ifc_offset_in_float_container = LogicalVec2 {
1411            inline: sequential_layout_state
1412                .floats
1413                .containing_block_info
1414                .inline_start,
1415            block: sequential_layout_state.current_containing_block_offset(),
1416        };
1417
1418        let ceiling = self.current_line_block_start_considering_placement_among_floats();
1419        let mut placement = PlacementAmongFloats::new(
1420            &sequential_layout_state.floats,
1421            ceiling + ifc_offset_in_float_container.block,
1422            LogicalVec2 {
1423                inline: potential_line_size.inline,
1424                block: potential_line_size.block,
1425            },
1426            &PaddingBorderMargin::zero(),
1427        );
1428
1429        let mut placement_rect = placement.place();
1430        placement_rect.start_corner -= ifc_offset_in_float_container;
1431        placement_rect
1432    }
1433
1434    /// Returns true if a new potential line size for the current line would require a line
1435    /// break. This takes into account floats and will also update the "placement among
1436    /// floats" for this line if the potential line size would not cause a line break.
1437    /// Thus, calling this method has side effects and should only be done while in the
1438    /// process of laying out line content that is always going to be committed to this
1439    /// line or the next.
1440    fn new_potential_line_size_causes_line_break(
1441        &mut self,
1442        potential_line_size: &LogicalVec2<Au>,
1443    ) -> bool {
1444        let containing_block = self.containing_block();
1445        let available_line_space = if self.sequential_layout_state.is_some() {
1446            self.current_line
1447                .placement_among_floats
1448                .get_or_init(|| self.place_line_among_floats(potential_line_size))
1449                .size
1450        } else {
1451            LogicalVec2 {
1452                inline: containing_block.size.inline,
1453                block: MAX_AU,
1454            }
1455        };
1456
1457        let inline_would_overflow = potential_line_size.inline > available_line_space.inline;
1458        let block_would_overflow = potential_line_size.block > available_line_space.block;
1459
1460        // The first content that is added to a line cannot trigger a line break and
1461        // the `white-space` propertly can also prevent all line breaking.
1462        let can_break = self.current_line.has_content;
1463
1464        // If this is the first content on the line and we already have a float placement,
1465        // that means that the placement was initialized by a leading float in the IFC.
1466        // This placement needs to be updated, because the first line content might push
1467        // the block start of the line downward. If there is no float placement, we want
1468        // to make one to properly set the block position of the line.
1469        if !can_break {
1470            // Even if we cannot break, adding content to this line might change its position.
1471            // In that case we need to redo our placement among floats.
1472            if self.sequential_layout_state.is_some() &&
1473                (inline_would_overflow || block_would_overflow)
1474            {
1475                let new_placement = self.place_line_among_floats(potential_line_size);
1476                self.current_line
1477                    .replace_placement_among_floats(new_placement);
1478            }
1479
1480            return false;
1481        }
1482
1483        // If the potential line is larger than the containing block we do not even need to consider
1484        // floats. We definitely have to do a linebreak.
1485        if potential_line_size.inline > containing_block.size.inline {
1486            return true;
1487        }
1488
1489        // Not fitting in the block space means that our block size has changed and we had a
1490        // placement among floats that is no longer valid. This same placement might just
1491        // need to be expanded or perhaps we need to line break.
1492        if block_would_overflow {
1493            // If we have a limited block size then we are wedging this line between floats.
1494            assert!(self.sequential_layout_state.is_some());
1495            let new_placement = self.place_line_among_floats(potential_line_size);
1496            if new_placement.start_corner.block !=
1497                self.current_line_block_start_considering_placement_among_floats()
1498            {
1499                return true;
1500            } else {
1501                self.current_line
1502                    .replace_placement_among_floats(new_placement);
1503                return false;
1504            }
1505        }
1506
1507        // Otherwise the new potential line size will require a newline if it fits in the
1508        // inline space available for this line. This space may be smaller than the
1509        // containing block if floats shrink the available inline space.
1510        inline_would_overflow
1511    }
1512
1513    fn defer_forced_line_break_at_character_offset(&mut self, line_break_offset: usize) {
1514        // If the current portion of the unbreakable segment does not fit on the current line
1515        // we need to put it on a new line *before* actually triggering the hard line break.
1516        if !self.unbreakable_segment_fits_on_line() {
1517            self.process_line_break(false /* forced_line_break */);
1518        }
1519
1520        // Defer the actual line break until we've cleared all ending inline boxes.
1521        self.force_line_break_before_new_content = Some(line_break_offset);
1522
1523        // In quirks mode, the line-height isn't automatically added to the line. If we consider a
1524        // forced line break a kind of preserved white space, quirks mode requires that we add the
1525        // line-height of the current element to the line box height.
1526        //
1527        // The exception here is `<br>` elements. They are implemented with `pre-line` in Servo, but
1528        // this is an implementation detail. The "magic" behavior of `<br>` elements is that they
1529        // add line-height to the line conditionally: only when they are on an otherwise empty line.
1530        let line_is_empty =
1531            !self.current_line_segment.has_content && !self.current_line.has_content;
1532        if !self.processing_br_element() || line_is_empty {
1533            let strut_size = self
1534                .current_inline_container_state()
1535                .strut_block_sizes
1536                .clone();
1537            self.update_unbreakable_segment_for_new_content(
1538                &strut_size,
1539                Au::zero(),
1540                SegmentContentFlags::empty(),
1541            );
1542        }
1543    }
1544
1545    fn possibly_flush_deferred_forced_line_break(&mut self) {
1546        let Some(line_break_character_offset) = self.force_line_break_before_new_content.take()
1547        else {
1548            return;
1549        };
1550
1551        self.commit_current_segment_to_line();
1552        self.process_line_break(true /* forced_line_break */);
1553
1554        self.current_line.starting_character_offset = line_break_character_offset + 1;
1555    }
1556
1557    fn push_line_item_to_unbreakable_segment(&mut self, line_item: LineItem) {
1558        self.current_line_segment
1559            .push_line_item(line_item, self.inline_box_state_stack.len());
1560    }
1561
1562    fn push_glyph_store_to_unbreakable_segment(
1563        &mut self,
1564        glyph_store: Arc<ShapedTextSlice>,
1565        text_run: &TextRun,
1566        info: &Arc<FontAndScriptInfo>,
1567        offsets: Option<TextRunOffsets>,
1568    ) {
1569        let inline_advance = glyph_store.total_advance();
1570        let flags = if glyph_store.is_whitespace() {
1571            SegmentContentFlags::from(text_run.inline_styles.style.borrow().get_inherited_text())
1572        } else {
1573            SegmentContentFlags::empty()
1574        };
1575
1576        let mut block_contribution = LineBlockSizes::zero();
1577        let quirks_mode = self.layout_context.style_context.quirks_mode() != QuirksMode::NoQuirks;
1578        let current_inline_container_state = self.current_inline_container_state();
1579        if quirks_mode && !flags.is_collapsible_whitespace() {
1580            // Normally, the strut is incorporated into the nested block size. In quirks mode though
1581            // if we find any text that isn't collapsed whitespace, we need to incorporate the strut.
1582            // TODO(mrobinson): This isn't quite right for situations where collapsible white space
1583            // ultimately does not collapse because it is between two other pieces of content.
1584            block_contribution.max_assign(&current_inline_container_state.strut_block_sizes);
1585        }
1586
1587        // If the metrics of this font don't match the default font, we are likely using another
1588        // font from the font list or a fallback and should incorporate its block size into the block
1589        // size of the container.
1590        let font_metrics = &info.font.metrics;
1591        if current_inline_container_state
1592            .font_metrics
1593            .block_metrics_meaningfully_differ(font_metrics)
1594        {
1595            // TODO(mrobinson): This value should probably be cached somewhere.
1596            let baseline_shift = effective_baseline_shift(
1597                &current_inline_container_state.style,
1598                self.inline_box_state_stack.last().map(|c| &c.base),
1599            );
1600            let mut font_block_conribution = current_inline_container_state
1601                .get_block_size_contribution(
1602                    baseline_shift,
1603                    font_metrics,
1604                    &current_inline_container_state.font_metrics,
1605                );
1606            font_block_conribution
1607                .adjust_for_baseline_offset(current_inline_container_state.baseline_offset);
1608            block_contribution.max_assign(&font_block_conribution);
1609        }
1610
1611        self.update_unbreakable_segment_for_new_content(&block_contribution, inline_advance, flags);
1612
1613        let current_inline_box_identifier = self.current_inline_box_identifier();
1614        if let Some(LineItem::TextRun(inline_box_identifier, line_item)) =
1615            self.current_line_segment.line_items.last_mut() &&
1616            *inline_box_identifier == current_inline_box_identifier &&
1617            line_item.merge_if_possible(info, &glyph_store, &offsets, &text_run.inline_styles)
1618        {
1619            return;
1620        }
1621
1622        self.push_line_item_to_unbreakable_segment(LineItem::TextRun(
1623            current_inline_box_identifier,
1624            TextRunLineItem {
1625                text: vec![glyph_store],
1626                base_fragment_info: text_run.base_fragment_info,
1627                inline_styles: text_run.inline_styles.clone(),
1628                info: info.clone(),
1629                offsets: offsets.map(Box::new),
1630                is_empty_for_text_cursor: false,
1631            },
1632        ));
1633    }
1634
1635    /// If the current line is empty and this [`InlineFormattingContext`] has a selection, push an
1636    /// empty [`LineItem::TextRun`] so that text carets can be placed on otherwise empty lines.
1637    fn possibly_push_empty_text_run_to_line_for_text_caret(&mut self) {
1638        let line_start_offset = self.current_line.starting_character_offset;
1639        let Some(shared_selection) = self.ifc.shared_selection.clone() else {
1640            return;
1641        };
1642        let offsets = TextRunOffsets {
1643            shared_selection,
1644            character_range: line_start_offset..line_start_offset + 1,
1645        };
1646
1647        // If the last content line item is a text item, then the placeholder for the text caret is not necessary.
1648        if self
1649            .current_line
1650            .line_items
1651            .iter()
1652            .rev()
1653            .find(|line_item| line_item.is_in_flow_content())
1654            .is_some_and(|line_item| matches!(line_item, LineItem::TextRun(..)))
1655        {
1656            return;
1657        }
1658
1659        let inline_container_state = self.current_inline_container_state();
1660        let Some(font) = inline_container_state.default_font.clone() else {
1661            return;
1662        };
1663
1664        self.push_line_item_to_unbreakable_segment(LineItem::TextRun(
1665            self.current_inline_box_identifier(),
1666            TextRunLineItem {
1667                text: Default::default(),
1668                base_fragment_info: BaseFragmentInfo::anonymous(),
1669                inline_styles: self.ifc.shared_inline_styles.clone(),
1670                info: Arc::new(FontAndScriptInfo::simple_for_font(font)),
1671                offsets: Some(Box::new(offsets)),
1672                is_empty_for_text_cursor: true,
1673            },
1674        ));
1675        self.current_line_segment.has_content = true;
1676        self.commit_current_segment_to_line();
1677    }
1678
1679    fn update_unbreakable_segment_for_new_content(
1680        &mut self,
1681        block_sizes_of_content: &LineBlockSizes,
1682        inline_size: Au,
1683        flags: SegmentContentFlags,
1684    ) {
1685        if flags.is_collapsible_whitespace() || flags.is_wrappable_and_hangable() {
1686            self.current_line_segment.trailing_whitespace_size = inline_size;
1687        } else {
1688            self.current_line_segment.trailing_whitespace_size = Au::zero();
1689        }
1690        if !flags.is_collapsible_whitespace() {
1691            self.current_line_segment.has_content = true;
1692        }
1693
1694        // This may or may not include the size of the strut depending on the quirks mode setting.
1695        let container_max_block_size = &self
1696            .current_inline_container_state()
1697            .nested_strut_block_sizes
1698            .clone();
1699        self.current_line_segment
1700            .max_block_size
1701            .max_assign(container_max_block_size);
1702        self.current_line_segment
1703            .max_block_size
1704            .max_assign(block_sizes_of_content);
1705
1706        self.current_line_segment.inline_size += inline_size;
1707
1708        // Propagate the whitespace setting to the current nesting level.
1709        *self
1710            .current_inline_container_state()
1711            .has_content
1712            .borrow_mut() = true;
1713        self.propagate_current_nesting_level_white_space_style();
1714    }
1715
1716    fn process_line_break(&mut self, forced_line_break: bool) {
1717        self.current_line_segment.trim_leading_whitespace();
1718        self.finish_current_line_and_reset(forced_line_break);
1719    }
1720
1721    fn potential_line_size(&self) -> LogicalVec2<Au> {
1722        LogicalVec2 {
1723            inline: self.current_line.inline_position + self.current_line_segment.inline_size,
1724            block: self
1725                .current_line_max_block_size_including_nested_containers()
1726                .max(&self.current_line_segment.max_block_size)
1727                .resolve(),
1728        }
1729    }
1730
1731    fn unbreakable_segment_fits_on_line(&mut self) -> bool {
1732        let potential_line_size_without_hanging_whitespace = self.potential_line_size() -
1733            LogicalVec2 {
1734                inline: self.current_line_segment.trailing_whitespace_size,
1735                block: Au::zero(),
1736            };
1737        !self.new_potential_line_size_causes_line_break(
1738            &potential_line_size_without_hanging_whitespace,
1739        )
1740    }
1741
1742    /// Process a soft wrap opportunity. This will either commit the current unbreakble
1743    /// segment to the current line, if it fits within the containing block and float
1744    /// placement boundaries, or do a line break and then commit the segment.
1745    fn process_soft_wrap_opportunity(&mut self) {
1746        if self.current_line_segment.line_items.is_empty() {
1747            return;
1748        }
1749        if self.text_wrap_mode == TextWrapMode::Nowrap {
1750            return;
1751        }
1752        if !self.unbreakable_segment_fits_on_line() {
1753            self.process_line_break(false /* forced_line_break */);
1754        }
1755        self.commit_current_segment_to_line();
1756    }
1757
1758    /// Commit the current unbrekable segment to the current line. In addition, this will
1759    /// place all floats in the unbreakable segment and expand the line dimensions.
1760    fn commit_current_segment_to_line(&mut self) {
1761        // The line segments might have no items and have content after processing a forced
1762        // linebreak on an empty line.
1763        if self.current_line_segment.line_items.is_empty() && !self.current_line_segment.has_content
1764        {
1765            return;
1766        }
1767
1768        if !self.current_line.has_content {
1769            self.current_line_segment.trim_leading_whitespace();
1770        }
1771
1772        self.current_line.inline_position += self.current_line_segment.inline_size;
1773        self.current_line.max_block_size = self
1774            .current_line_max_block_size_including_nested_containers()
1775            .max(&self.current_line_segment.max_block_size);
1776        let line_inline_size_without_trailing_whitespace =
1777            self.current_line.inline_position - self.current_line_segment.trailing_whitespace_size;
1778
1779        // Place all floats in this unbreakable segment.
1780        let mut segment_items = mem::take(&mut self.current_line_segment.line_items);
1781        for item in segment_items.iter_mut() {
1782            if let LineItem::Float(_, float_item) = item {
1783                self.place_float_line_item_for_commit_to_line(
1784                    float_item,
1785                    line_inline_size_without_trailing_whitespace,
1786                );
1787            }
1788        }
1789
1790        // If the current line was never placed among floats, we need to do that now based on the
1791        // new size. Calling `new_potential_line_size_causes_line_break()` here triggers the
1792        // new line to be positioned among floats. This should never ask for a line
1793        // break because it is the first content on the line.
1794        if self.current_line.line_items.is_empty() {
1795            let will_break = self.new_potential_line_size_causes_line_break(&LogicalVec2 {
1796                inline: line_inline_size_without_trailing_whitespace,
1797                block: self.current_line_segment.max_block_size.resolve(),
1798            });
1799            assert!(!will_break);
1800        }
1801
1802        self.current_line.line_items.extend(segment_items);
1803        self.current_line.has_content |= self.current_line_segment.has_content;
1804        self.current_line.has_inline_pbm |= self.current_line_segment.has_inline_pbm;
1805
1806        self.current_line_segment.reset();
1807    }
1808
1809    #[inline]
1810    fn containing_block(&self) -> &ContainingBlock<'_> {
1811        self.placement_state.containing_block
1812    }
1813}
1814
1815bitflags! {
1816    struct SegmentContentFlags: u8 {
1817        const COLLAPSIBLE_WHITESPACE = 0b00000001;
1818        const WRAPPABLE_AND_HANGABLE_WHITESPACE = 0b00000010;
1819    }
1820}
1821
1822impl SegmentContentFlags {
1823    fn is_collapsible_whitespace(&self) -> bool {
1824        self.contains(Self::COLLAPSIBLE_WHITESPACE)
1825    }
1826
1827    fn is_wrappable_and_hangable(&self) -> bool {
1828        self.contains(Self::WRAPPABLE_AND_HANGABLE_WHITESPACE)
1829    }
1830}
1831
1832impl From<&InheritedText> for SegmentContentFlags {
1833    fn from(style_text: &InheritedText) -> Self {
1834        let mut flags = Self::empty();
1835
1836        // White-space with `white-space-collapse: break-spaces` or `white-space-collapse: preserve`
1837        // never collapses.
1838        if !matches!(
1839            style_text.white_space_collapse,
1840            WhiteSpaceCollapse::Preserve | WhiteSpaceCollapse::BreakSpaces
1841        ) {
1842            flags.insert(Self::COLLAPSIBLE_WHITESPACE);
1843        }
1844
1845        // White-space with `white-space-collapse: break-spaces` never hangs and always takes up
1846        // space.
1847        if style_text.text_wrap_mode == TextWrapMode::Wrap &&
1848            style_text.white_space_collapse != WhiteSpaceCollapse::BreakSpaces
1849        {
1850            flags.insert(Self::WRAPPABLE_AND_HANGABLE_WHITESPACE);
1851        }
1852        flags
1853    }
1854}
1855
1856impl InlineFormattingContext {
1857    #[servo_tracing::instrument(name = "InlineFormattingContext::new_with_builder", skip_all)]
1858    fn new_with_builder(
1859        mut builder: InlineFormattingContextBuilder,
1860        layout_context: &LayoutContext,
1861        has_first_formatted_line: bool,
1862        is_single_line_text_input: bool,
1863        starting_bidi_level: Level,
1864    ) -> Self {
1865        // This is to prevent a double borrow.
1866        let text_content: String = builder.text_segments.into_iter().collect();
1867
1868        let bidi_info = BidiInfo::new(&text_content, Some(starting_bidi_level));
1869        let has_right_to_left_content = bidi_info.has_rtl();
1870        let shared_inline_styles = builder
1871            .shared_inline_styles_stack
1872            .last()
1873            .expect("Should have at least one SharedInlineStyle for the root of an IFC")
1874            .clone();
1875        let (word_break, line_break, lang) = {
1876            let styles = shared_inline_styles.style.borrow();
1877            let text_style = styles.get_inherited_text();
1878            (
1879                text_style.word_break,
1880                text_style.line_break,
1881                styles.get_font()._x_lang.clone(),
1882            )
1883        };
1884
1885        let mut options = LineBreakOptions::default();
1886
1887        options.strictness = match line_break {
1888            LineBreak::Loose => LineBreakStrictness::Loose,
1889            LineBreak::Normal => LineBreakStrictness::Normal,
1890            LineBreak::Strict => LineBreakStrictness::Strict,
1891            LineBreak::Anywhere => LineBreakStrictness::Anywhere,
1892            // For `auto`, the UA determines the set of line-breaking restrictions to use.
1893            // So it's fine if we always treat it as `normal`.
1894            LineBreak::Auto => LineBreakStrictness::Normal,
1895        };
1896        options.word_option = match word_break {
1897            WordBreak::Normal => LineBreakWordOption::Normal,
1898            WordBreak::BreakAll => LineBreakWordOption::BreakAll,
1899            WordBreak::KeepAll => LineBreakWordOption::KeepAll,
1900        };
1901        // Enable Chinese/Japanese line breaking behavior when this inline formatting context
1902        // has a Japanese or Chinese language set.
1903        options.ja_zh = {
1904            lang.0.parse::<LanguageIdentifier>().is_ok_and(|lang_id| {
1905                const JA: Language = language!("ja");
1906                const ZH: Language = language!("zh");
1907                matches!(lang_id.language, JA | ZH)
1908            })
1909        };
1910
1911        let mut new_linebreaker = LineBreaker::new(text_content.as_str(), options);
1912        for item in &mut builder.inline_items {
1913            match item {
1914                InlineItem::TextRun(text_run) => {
1915                    text_run.borrow_mut().segment_and_shape(
1916                        &text_content,
1917                        layout_context,
1918                        &mut new_linebreaker,
1919                        &bidi_info,
1920                    );
1921                },
1922                InlineItem::StartInlineBox(inline_box) => {
1923                    let inline_box = &mut *inline_box.borrow_mut();
1924                    if let Some(font) = get_font_for_first_font_for_style(
1925                        &inline_box.base.style,
1926                        &layout_context.font_context,
1927                    ) {
1928                        inline_box.default_font = Some(font);
1929                    }
1930                },
1931                InlineItem::Atomic(_, index_in_text, bidi_level) => {
1932                    *bidi_level = bidi_info.levels[*index_in_text];
1933                },
1934                InlineItem::OutOfFlowAbsolutelyPositionedBox(..) |
1935                InlineItem::OutOfFlowFloatBox(_) |
1936                InlineItem::EndInlineBox |
1937                InlineItem::BlockLevel { .. } => {},
1938            }
1939        }
1940
1941        let default_font = get_font_for_first_font_for_style(
1942            &shared_inline_styles.style.borrow(),
1943            &layout_context.font_context,
1944        );
1945
1946        InlineFormattingContext {
1947            text_content,
1948            inline_items: builder.inline_items,
1949            inline_boxes: builder.inline_boxes,
1950            shared_inline_styles,
1951            default_font,
1952            has_first_formatted_line,
1953            contains_floats: builder.contains_floats,
1954            is_single_line_text_input,
1955            has_right_to_left_content,
1956            shared_selection: builder.shared_selection,
1957            tab_stop_advance: Default::default(),
1958        }
1959    }
1960
1961    pub(crate) fn repair_style(
1962        &self,
1963        context: &SharedStyleContext,
1964        node: &ServoLayoutNode,
1965        new_style: &ServoArc<ComputedValues>,
1966    ) {
1967        *self.shared_inline_styles.style.borrow_mut() = new_style.clone();
1968        *self.shared_inline_styles.selected.borrow_mut() = node.selected_style(context);
1969    }
1970
1971    fn inline_start_for_first_line(&self, containing_block: IndefiniteContainingBlock) -> Au {
1972        if !self.has_first_formatted_line {
1973            return Au::zero();
1974        }
1975        containing_block
1976            .style
1977            .get_inherited_text()
1978            .text_indent
1979            .length
1980            .to_used_value(containing_block.size.inline.unwrap_or_default())
1981    }
1982
1983    pub(super) fn layout(
1984        &self,
1985        layout_context: &LayoutContext,
1986        positioning_context: &mut PositioningContext,
1987        containing_block: &ContainingBlock,
1988        sequential_layout_state: Option<&mut SequentialLayoutState>,
1989        collapsible_with_parent_start_margin: CollapsibleWithParentStartMargin,
1990    ) -> IndependentFormattingContextLayoutResult {
1991        // Clear any cached inline fragments from previous layouts.
1992        for inline_box in self.inline_boxes.iter() {
1993            inline_box.borrow().base.clear_fragments();
1994        }
1995
1996        let style = containing_block.style;
1997
1998        let style_text = containing_block.style.get_inherited_text();
1999        let mut inline_container_state_flags = InlineContainerStateFlags::empty();
2000        if inline_container_needs_strut(style, layout_context, None) {
2001            inline_container_state_flags.insert(InlineContainerStateFlags::CREATE_STRUT);
2002        }
2003        if self.is_single_line_text_input {
2004            inline_container_state_flags
2005                .insert(InlineContainerStateFlags::IS_SINGLE_LINE_TEXT_INPUT);
2006        }
2007        let placement_state =
2008            PlacementState::new(collapsible_with_parent_start_margin, containing_block);
2009
2010        let mut layout = InlineFormattingContextLayout {
2011            positioning_context,
2012            placement_state,
2013            sequential_layout_state,
2014            layout_context,
2015            ifc: self,
2016            fragments: Vec::new(),
2017            current_line: LineUnderConstruction::new(LogicalVec2 {
2018                inline: self.inline_start_for_first_line(containing_block.into()),
2019                block: Au::zero(),
2020            }),
2021            root_nesting_level: InlineContainerState::new(
2022                style.to_arc(),
2023                inline_container_state_flags,
2024                None, /* parent_container */
2025                self.default_font.clone(),
2026            ),
2027            inline_box_state_stack: Vec::new(),
2028            inline_box_states: Vec::with_capacity(self.inline_boxes.len()),
2029            current_line_segment: UnbreakableSegmentUnderConstruction::new(),
2030            force_line_break_before_new_content: None,
2031            deferred_br_clear: Clear::None,
2032            have_deferred_soft_wrap_opportunity: false,
2033            depends_on_block_constraints: false,
2034            white_space_collapse: style_text.white_space_collapse,
2035            text_wrap_mode: style_text.text_wrap_mode,
2036        };
2037
2038        for item in self.inline_items.iter() {
2039            // Any new box should flush a pending hard line break.
2040            if !matches!(item, InlineItem::EndInlineBox) {
2041                layout.possibly_flush_deferred_forced_line_break();
2042            }
2043
2044            match item {
2045                InlineItem::StartInlineBox(inline_box) => {
2046                    layout.start_inline_box(&inline_box.borrow());
2047                },
2048                InlineItem::EndInlineBox => layout.finish_inline_box(),
2049                InlineItem::TextRun(run) => run.borrow().layout_into_line_items(&mut layout),
2050                InlineItem::Atomic(atomic_formatting_context, offset_in_text, bidi_level) => {
2051                    atomic_formatting_context.borrow().layout_into_line_items(
2052                        &mut layout,
2053                        *offset_in_text,
2054                        *bidi_level,
2055                    );
2056                },
2057                InlineItem::OutOfFlowAbsolutelyPositionedBox(positioned_box, _) => {
2058                    layout.push_line_item_to_unbreakable_segment(LineItem::AbsolutelyPositioned(
2059                        layout.current_inline_box_identifier(),
2060                        AbsolutelyPositionedLineItem {
2061                            absolutely_positioned_box: positioned_box.clone(),
2062                            preceding_line_content_would_produce_phantom_line: layout
2063                                .current_line
2064                                .is_phantom() &&
2065                                layout.current_line_segment.is_phantom(),
2066                        },
2067                    ));
2068                },
2069                InlineItem::OutOfFlowFloatBox(float_box) => {
2070                    float_box.borrow().layout_into_line_items(&mut layout);
2071                },
2072                InlineItem::BlockLevel(block_level) => {
2073                    block_level.borrow().layout_into_line_items(&mut layout);
2074                },
2075            }
2076        }
2077
2078        layout.finish_last_line();
2079        let (content_block_size, collapsible_margins_in_children, baselines) =
2080            layout.placement_state.finish();
2081
2082        IndependentFormattingContextLayoutResult {
2083            fragments: layout.fragments,
2084            content_block_size,
2085            collapsible_margins_in_children,
2086            baselines,
2087            depends_on_block_constraints: layout.depends_on_block_constraints,
2088            content_inline_size_for_table: None,
2089            specific_layout_info: None,
2090        }
2091    }
2092
2093    fn next_character_prevents_soft_wrap_opportunity(&self, index: usize) -> bool {
2094        let Some(character) = self.text_content[index..].chars().nth(1) else {
2095            return false;
2096        };
2097        char_prevents_soft_wrap_opportunity_when_before_or_after_atomic(character)
2098    }
2099
2100    fn previous_character_prevents_soft_wrap_opportunity(&self, index: usize) -> bool {
2101        let Some(character) = self.text_content[0..index].chars().next_back() else {
2102            return false;
2103        };
2104        char_prevents_soft_wrap_opportunity_when_before_or_after_atomic(character)
2105    }
2106
2107    pub(crate) fn find_block_margin_collapsing_with_parent(
2108        &self,
2109        layout_context: &LayoutContext,
2110        collected_margin: &mut CollapsedMargin,
2111        containing_block_for_children: &ContainingBlock,
2112    ) -> bool {
2113        // Margins can't collapse through line boxes, unless they are phantom line boxes.
2114        // <https://drafts.csswg.org/css-inline-3/#invisible-line-boxes>
2115        // > Line boxes that contain no text, no preserved white space, no inline boxes with non-zero
2116        // > inline-axis margins, padding, or borders, and no other in-flow content (such as atomic
2117        // > inlines or ruby annotations), and do not end with a forced line break are phantom line boxes.
2118        let mut nesting_levels_from_nonzero_end_pbm: u32 = 1;
2119        let mut items_iter = self.inline_items.iter();
2120        items_iter.all(|inline_item| match inline_item {
2121            InlineItem::StartInlineBox(inline_box) => {
2122                let pbm = inline_box
2123                    .borrow()
2124                    .layout_style()
2125                    .padding_border_margin(containing_block_for_children);
2126                if pbm.padding.inline_end.is_zero() &&
2127                    pbm.border.inline_end.is_zero() &&
2128                    pbm.margin.inline_end.auto_is(Au::zero).is_zero()
2129                {
2130                    nesting_levels_from_nonzero_end_pbm += 1;
2131                } else {
2132                    nesting_levels_from_nonzero_end_pbm = 0;
2133                }
2134                pbm.padding.inline_start.is_zero() &&
2135                    pbm.border.inline_start.is_zero() &&
2136                    pbm.margin.inline_start.auto_is(Au::zero).is_zero()
2137            },
2138            InlineItem::EndInlineBox => {
2139                if nesting_levels_from_nonzero_end_pbm == 0 {
2140                    false
2141                } else {
2142                    nesting_levels_from_nonzero_end_pbm -= 1;
2143                    true
2144                }
2145            },
2146            InlineItem::TextRun(text_run) => {
2147                let text_run = &*text_run.borrow();
2148                let parent_style = text_run.inline_styles.style.borrow();
2149                text_run.items.iter().all(|item| match item {
2150                    TextRunItem::LineBreak { .. } => false,
2151                    TextRunItem::Tab { .. } => false,
2152                    TextRunItem::TextSegment(segment) => segment.runs.iter().all(|run| {
2153                        run.is_whitespace() &&
2154                            !matches!(
2155                                parent_style.get_inherited_text().white_space_collapse,
2156                                WhiteSpaceCollapse::Preserve | WhiteSpaceCollapse::BreakSpaces
2157                            )
2158                    }),
2159                })
2160            },
2161            InlineItem::OutOfFlowAbsolutelyPositionedBox(..) => true,
2162            InlineItem::OutOfFlowFloatBox(..) => true,
2163            InlineItem::Atomic(..) => false,
2164            InlineItem::BlockLevel(block_level) => block_level
2165                .borrow()
2166                .find_block_margin_collapsing_with_parent(
2167                    layout_context,
2168                    collected_margin,
2169                    containing_block_for_children,
2170                ),
2171        })
2172    }
2173
2174    pub(crate) fn attached_to_tree(&self, layout_box: WeakLayoutBox) {
2175        let mut parent_box_stack = Vec::new();
2176        let current_parent_box = |parent_box_stack: &[WeakLayoutBox]| {
2177            parent_box_stack.last().unwrap_or(&layout_box).clone()
2178        };
2179        for inline_item in &self.inline_items {
2180            match inline_item {
2181                InlineItem::StartInlineBox(inline_box) => {
2182                    inline_box
2183                        .borrow_mut()
2184                        .base
2185                        .parent_box
2186                        .replace(current_parent_box(&parent_box_stack));
2187                    parent_box_stack.push(WeakLayoutBox::InlineLevel(
2188                        WeakInlineItem::StartInlineBox(inline_box.downgrade()),
2189                    ));
2190                },
2191                InlineItem::EndInlineBox => {
2192                    parent_box_stack.pop();
2193                },
2194                InlineItem::TextRun(text_run) => {
2195                    text_run
2196                        .borrow_mut()
2197                        .parent_box
2198                        .replace(current_parent_box(&parent_box_stack));
2199                },
2200                _ => inline_item.with_base_mut(|base| {
2201                    base.parent_box
2202                        .replace(current_parent_box(&parent_box_stack));
2203                }),
2204            }
2205        }
2206    }
2207
2208    pub(crate) fn next_tab_stop_after_inline_advance(&self, current_inline_advance: Au) -> Au {
2209        let Some(font) = self.default_font.as_ref() else {
2210            return Au::zero();
2211        };
2212
2213        let tab_stop_advance = *self.tab_stop_advance.get_or_init(|| {
2214            let root_style = self.shared_inline_styles.style.borrow();
2215            let inherited_text_style = root_style.get_inherited_text();
2216            let font_size = root_style.get_font().font_size.computed_size().into();
2217            let letter_spacing = inherited_text_style
2218                .letter_spacing
2219                .0
2220                .to_used_value(font_size);
2221            let word_spacing = inherited_text_style.word_spacing.to_used_value(font_size);
2222
2223            match root_style.get_inherited_text().tab_size {
2224                // Each "space" character in the tab is considered both a letter and a word separator for
2225                // the purposes of applying word spacing and letter spacing.
2226                style::values::generics::length::LengthOrNumber::Number(number_of_spaces) => {
2227                    (font.metrics.space_advance + word_spacing + letter_spacing)
2228                        .scale_by(number_of_spaces.0)
2229                },
2230                // When a length is provided we do not apply word spacing or letter spacing.
2231                style::values::generics::length::LengthOrNumber::Length(length) => length.into(),
2232            }
2233        });
2234
2235        if tab_stop_advance.is_zero() {
2236            return Au::zero();
2237        }
2238
2239        // From <https://drafts.csswg.org/css-text-4/#ref-for-tab-size-dfn>
2240        // > If this distance is less than 0.5ch, then the subsequent tab stop is used instead.
2241        // From <https://drafts.csswg.org/css-values/#ch>
2242        // > In the cases where it is impossible or impractical to determine the measure of the “0”
2243        // > glyph, it must be assumed to be 0.5em wide by 1em tall.
2244        let half_ch_advance = font
2245            .metrics
2246            .zero_horizontal_advance
2247            .unwrap_or(font.metrics.em_size.scale_by(0.5))
2248            .scale_by(0.5);
2249        let number_of_tab_stops =
2250            (current_inline_advance + half_ch_advance).to_f32_px() / tab_stop_advance.to_f32_px();
2251        let number_of_tab_stops = number_of_tab_stops.ceil();
2252        tab_stop_advance.scale_by(number_of_tab_stops) - current_inline_advance
2253    }
2254}
2255
2256impl InlineContainerState {
2257    fn new(
2258        style: ServoArc<ComputedValues>,
2259        flags: InlineContainerStateFlags,
2260        parent_container: Option<&InlineContainerState>,
2261        default_font: Option<FontRef>,
2262    ) -> Self {
2263        let font_metrics = default_font
2264            .as_ref()
2265            .map(|font| font.metrics.clone())
2266            .unwrap_or_else(FontMetrics::empty);
2267        let mut baseline_offset = Au::zero();
2268        let mut strut_block_sizes = {
2269            Self::get_block_sizes_with_style(
2270                effective_baseline_shift(&style, parent_container),
2271                &style,
2272                &font_metrics,
2273                &font_metrics,
2274                &flags,
2275            )
2276        };
2277
2278        if let Some(parent_container) = parent_container {
2279            // The baseline offset from `vertical-align` might adjust where our block size contribution is
2280            // within the line.
2281            baseline_offset = parent_container.get_cumulative_baseline_offset_for_child(
2282                style.clone_alignment_baseline(),
2283                style.clone_baseline_shift(),
2284                &strut_block_sizes,
2285            );
2286            strut_block_sizes.adjust_for_baseline_offset(baseline_offset);
2287        }
2288
2289        let mut nested_block_sizes = parent_container
2290            .map(|container| container.nested_strut_block_sizes.clone())
2291            .unwrap_or_else(LineBlockSizes::zero);
2292        if flags.contains(InlineContainerStateFlags::CREATE_STRUT) {
2293            nested_block_sizes.max_assign(&strut_block_sizes);
2294        }
2295
2296        Self {
2297            style,
2298            flags,
2299            has_content: RefCell::new(false),
2300            nested_strut_block_sizes: nested_block_sizes,
2301            strut_block_sizes,
2302            baseline_offset,
2303            default_font,
2304            font_metrics,
2305        }
2306    }
2307
2308    fn get_block_sizes_with_style(
2309        baseline_shift: BaselineShift,
2310        style: &ComputedValues,
2311        font_metrics: &FontMetrics,
2312        font_metrics_of_first_font: &FontMetrics,
2313        flags: &InlineContainerStateFlags,
2314    ) -> LineBlockSizes {
2315        let line_height = line_height(style, font_metrics, flags);
2316
2317        if !is_baseline_relative(baseline_shift) {
2318            return LineBlockSizes {
2319                line_height,
2320                baseline_relative_size_for_line_height: None,
2321                size_for_baseline_positioning: BaselineRelativeSize::zero(),
2322            };
2323        }
2324
2325        // From https://drafts.csswg.org/css-inline/#inline-height
2326        // > If line-height computes to `normal` and either `text-box-edge` is `leading` or this
2327        // > is the root inline box, the font’s line gap metric may also be incorporated
2328        // > into A and D by adding half to each side as half-leading.
2329        //
2330        // `text-box-edge` isn't implemented (and this is a draft specification), so it's
2331        // always effectively `leading`, which means we always take into account the line gap
2332        // when `line-height` is normal.
2333        let mut ascent = font_metrics.ascent;
2334        let mut descent = font_metrics.descent;
2335        if style.get_font().line_height == LineHeight::Normal {
2336            let half_leading_from_line_gap =
2337                (font_metrics.line_gap - descent - ascent).scale_by(0.5);
2338            ascent += half_leading_from_line_gap;
2339            descent += half_leading_from_line_gap;
2340        }
2341
2342        // The ascent and descent we use for computing the line's final line height isn't
2343        // the same the ascent and descent we use for finding the baseline. For finding
2344        // the baseline we want the content rect.
2345        let size_for_baseline_positioning = BaselineRelativeSize { ascent, descent };
2346
2347        // From https://drafts.csswg.org/css-inline/#inline-height
2348        // > When its computed line-height is not normal, its layout bounds are derived solely
2349        // > from metrics of its first available font (ignoring glyphs from other fonts), and
2350        // > leading is used to adjust the effective A and D to add up to the used line-height.
2351        // > Calculate the leading L as L = line-height - (A + D). Half the leading (its
2352        // > half-leading) is added above A of the first available font, and the other half
2353        // > below D of the first available font, giving an effective ascent above the baseline
2354        // > of A′ = A + L/2, and an effective descent of D′ = D + L/2.
2355        //
2356        // Note that leading might be negative here and the line-height might be zero. In
2357        // the case where the height is zero, ascent and descent will move to the same
2358        // point in the block axis.  Even though the contribution to the line height is
2359        // zero in this case, the line may get some height when taking them into
2360        // considering with other zero line height boxes that converge on other block axis
2361        // locations when using the above formula.
2362        if style.get_font().line_height != LineHeight::Normal {
2363            ascent = font_metrics_of_first_font.ascent;
2364            descent = font_metrics_of_first_font.descent;
2365            let half_leading = (line_height - (ascent + descent)).scale_by(0.5);
2366            // We want the sum of `ascent` and `descent` to equal `line_height`.
2367            // If we just add `half_leading` to both, then we may not get `line_height`
2368            // due to precision limitations of `Au`. Instead, we set `descent` to
2369            // the value that will guarantee the correct sum.
2370            ascent += half_leading;
2371            descent = line_height - ascent;
2372        }
2373
2374        LineBlockSizes {
2375            line_height,
2376            baseline_relative_size_for_line_height: Some(BaselineRelativeSize { ascent, descent }),
2377            size_for_baseline_positioning,
2378        }
2379    }
2380
2381    fn get_block_size_contribution(
2382        &self,
2383        baseline_shift: BaselineShift,
2384        font_metrics: &FontMetrics,
2385        font_metrics_of_first_font: &FontMetrics,
2386    ) -> LineBlockSizes {
2387        Self::get_block_sizes_with_style(
2388            baseline_shift,
2389            &self.style,
2390            font_metrics,
2391            font_metrics_of_first_font,
2392            &self.flags,
2393        )
2394    }
2395
2396    fn get_cumulative_baseline_offset_for_child(
2397        &self,
2398        child_alignment_baseline: AlignmentBaseline,
2399        child_baseline_shift: BaselineShift,
2400        child_block_size: &LineBlockSizes,
2401    ) -> Au {
2402        let block_size = self.get_block_size_contribution(
2403            child_baseline_shift.clone(),
2404            &self.font_metrics,
2405            &self.font_metrics,
2406        );
2407        self.baseline_offset +
2408            match child_alignment_baseline {
2409                AlignmentBaseline::Baseline => Au::zero(),
2410                AlignmentBaseline::TextTop => {
2411                    child_block_size.size_for_baseline_positioning.ascent - self.font_metrics.ascent
2412                },
2413                AlignmentBaseline::Middle => {
2414                    // "Align the vertical midpoint of the box with the baseline of the parent
2415                    // box plus half the x-height of the parent."
2416                    (child_block_size.size_for_baseline_positioning.ascent -
2417                        child_block_size.size_for_baseline_positioning.descent -
2418                        self.font_metrics.x_height)
2419                        .scale_by(0.5)
2420                },
2421                AlignmentBaseline::TextBottom => {
2422                    self.font_metrics.descent -
2423                        child_block_size.size_for_baseline_positioning.descent
2424                },
2425                AlignmentBaseline::Alphabetic |
2426                AlignmentBaseline::Ideographic |
2427                AlignmentBaseline::Central |
2428                AlignmentBaseline::Mathematical |
2429                AlignmentBaseline::Hanging => {
2430                    unreachable!("Got alignment-baseline value that should be disabled in Stylo")
2431                },
2432            } +
2433            match child_baseline_shift {
2434                // `top` and `bottom are not actually relative to the baseline, but this value is unused
2435                // in those cases.
2436                // TODO: We should distinguish these from `baseline` in order to implement "aligned subtrees" properly.
2437                // See https://drafts.csswg.org/css2/#aligned-subtree.
2438                BaselineShift::Keyword(
2439                    BaselineShiftKeyword::Top |
2440                    BaselineShiftKeyword::Bottom |
2441                    BaselineShiftKeyword::Center,
2442                ) => Au::zero(),
2443                BaselineShift::Keyword(BaselineShiftKeyword::Sub) => {
2444                    block_size.resolve().scale_by(FONT_SUBSCRIPT_OFFSET_RATIO)
2445                },
2446                BaselineShift::Keyword(BaselineShiftKeyword::Super) => {
2447                    -block_size.resolve().scale_by(FONT_SUPERSCRIPT_OFFSET_RATIO)
2448                },
2449                BaselineShift::Length(length_percentage) => {
2450                    -length_percentage.to_used_value(child_block_size.line_height)
2451                },
2452            }
2453    }
2454}
2455
2456impl IndependentFormattingContext {
2457    fn layout_into_line_items(
2458        &self,
2459        layout: &mut InlineFormattingContextLayout,
2460        offset_in_text: usize,
2461        bidi_level: Level,
2462    ) {
2463        // We need to know the inline size of the atomic before deciding whether to do the line break.
2464        let mut child_positioning_context = PositioningContext::default();
2465        let IndependentFloatOrAtomicLayoutResult {
2466            mut fragment,
2467            baselines,
2468            pbm_sums,
2469        } = self.layout_float_or_atomic_inline(
2470            layout.layout_context,
2471            &mut child_positioning_context,
2472            layout.containing_block(),
2473        );
2474
2475        // If this Fragment's layout depends on the block size of the containing block,
2476        // then the entire layout of the inline formatting context does as well.
2477        layout.depends_on_block_constraints |= fragment.base.flags.contains(
2478            FragmentFlags::SIZE_DEPENDS_ON_BLOCK_CONSTRAINTS_AND_CAN_BE_CHILD_OF_FLEX_ITEM,
2479        );
2480
2481        // Offset the content rectangle by the physical offset of the padding, border, and margin.
2482        let container_writing_mode = layout.containing_block().style.writing_mode;
2483        let pbm_physical_offset = pbm_sums
2484            .start_offset()
2485            .to_physical_size(container_writing_mode);
2486        fragment.base.translate_rect(pbm_physical_offset);
2487
2488        // Apply baselines.
2489        fragment = fragment.with_baselines(baselines);
2490
2491        // Lay out absolutely positioned children if this new atomic establishes a containing block
2492        // for absolutes.
2493        let positioning_context = if self.is_replaced() {
2494            None
2495        } else {
2496            if fragment
2497                .style()
2498                .establishes_containing_block_for_absolute_descendants(fragment.base.flags)
2499            {
2500                child_positioning_context
2501                    .layout_collected_children(layout.layout_context, &mut fragment);
2502            }
2503            Some(child_positioning_context)
2504        };
2505
2506        if layout.text_wrap_mode == TextWrapMode::Wrap &&
2507            !layout
2508                .ifc
2509                .previous_character_prevents_soft_wrap_opportunity(offset_in_text)
2510        {
2511            layout.process_soft_wrap_opportunity();
2512        }
2513
2514        let size = pbm_sums.sum() + fragment.base.rect().size.to_logical(container_writing_mode);
2515        let baseline_offset = self
2516            .pick_baseline(&fragment.baselines(container_writing_mode))
2517            .map(|baseline| pbm_sums.block_start + baseline)
2518            .unwrap_or(size.block);
2519
2520        let (block_sizes, baseline_offset_in_parent) =
2521            self.get_block_sizes_and_baseline_offset(layout, size.block, baseline_offset);
2522        layout.update_unbreakable_segment_for_new_content(
2523            &block_sizes,
2524            size.inline,
2525            SegmentContentFlags::empty(),
2526        );
2527
2528        let fragment = Arc::new(fragment);
2529        self.base.set_fragment(Fragment::Box(fragment.clone()));
2530
2531        layout.push_line_item_to_unbreakable_segment(LineItem::Atomic(
2532            layout.current_inline_box_identifier(),
2533            AtomicLineItem {
2534                fragment,
2535                size,
2536                positioning_context,
2537                baseline_offset_in_parent,
2538                baseline_offset_in_item: baseline_offset,
2539                bidi_level,
2540            },
2541        ));
2542
2543        // If there's a soft wrap opportunity following this atomic, defer a soft wrap opportunity
2544        // for when we next process text content.
2545        if !layout
2546            .ifc
2547            .next_character_prevents_soft_wrap_opportunity(offset_in_text)
2548        {
2549            layout.have_deferred_soft_wrap_opportunity = true;
2550        }
2551    }
2552
2553    /// Picks either the first or the last baseline, depending on `baseline-source`.
2554    /// TODO: clarify that this is not to be used for box alignment in flex/grid
2555    /// <https://drafts.csswg.org/css-inline/#baseline-source>
2556    fn pick_baseline(&self, baselines: &Baselines) -> Option<Au> {
2557        match self.style().clone_baseline_source() {
2558            BaselineSource::First => baselines.first,
2559            BaselineSource::Last => baselines.last,
2560            BaselineSource::Auto if self.is_block_container() => baselines.last,
2561            BaselineSource::Auto => baselines.first,
2562        }
2563    }
2564
2565    fn get_block_sizes_and_baseline_offset(
2566        &self,
2567        ifc: &InlineFormattingContextLayout,
2568        block_size: Au,
2569        baseline_offset_in_content_area: Au,
2570    ) -> (LineBlockSizes, Au) {
2571        let mut contribution = if !is_baseline_relative(self.style().clone_baseline_shift()) {
2572            LineBlockSizes {
2573                line_height: block_size,
2574                baseline_relative_size_for_line_height: None,
2575                size_for_baseline_positioning: BaselineRelativeSize::zero(),
2576            }
2577        } else {
2578            let baseline_relative_size = BaselineRelativeSize {
2579                ascent: baseline_offset_in_content_area,
2580                descent: block_size - baseline_offset_in_content_area,
2581            };
2582            LineBlockSizes {
2583                line_height: block_size,
2584                baseline_relative_size_for_line_height: Some(baseline_relative_size.clone()),
2585                size_for_baseline_positioning: baseline_relative_size,
2586            }
2587        };
2588
2589        let style = self.style();
2590        let baseline_offset = ifc
2591            .current_inline_container_state()
2592            .get_cumulative_baseline_offset_for_child(
2593                style.clone_alignment_baseline(),
2594                style.clone_baseline_shift(),
2595                &contribution,
2596            );
2597        contribution.adjust_for_baseline_offset(baseline_offset);
2598
2599        (contribution, baseline_offset)
2600    }
2601}
2602
2603impl FloatBox {
2604    fn layout_into_line_items(&self, layout: &mut InlineFormattingContextLayout) {
2605        let fragment = Arc::new(self.layout(
2606            layout.layout_context,
2607            layout.positioning_context,
2608            layout.placement_state.containing_block,
2609        ));
2610
2611        self.contents
2612            .base
2613            .set_fragment(Fragment::Box(fragment.clone()));
2614        layout.push_line_item_to_unbreakable_segment(LineItem::Float(
2615            layout.current_inline_box_identifier(),
2616            FloatLineItem {
2617                fragment,
2618                needs_placement: true,
2619            },
2620        ));
2621    }
2622}
2623
2624fn place_pending_floats(ifc: &mut InlineFormattingContextLayout, line_items: &[LineItem]) {
2625    for item in line_items.iter() {
2626        if let LineItem::Float(_, float_line_item) = item &&
2627            float_line_item.needs_placement
2628        {
2629            ifc.place_float_fragment(&float_line_item.fragment);
2630        }
2631    }
2632}
2633
2634fn line_height(
2635    parent_style: &ComputedValues,
2636    font_metrics: &FontMetrics,
2637    flags: &InlineContainerStateFlags,
2638) -> Au {
2639    let font = parent_style.get_font();
2640    let font_size = font.font_size.computed_size();
2641    let mut line_height = match font.line_height {
2642        LineHeight::Normal => font_metrics.line_gap,
2643        LineHeight::Number(number) => (font_size * number.0).into(),
2644        LineHeight::Length(length) => length.0.into(),
2645    };
2646
2647    // The line height of a single-line text input's inner text container is clamped to
2648    // the size of `normal`.
2649    // <https://html.spec.whatwg.org/multipage/#the-input-element-as-a-text-entry-widget>
2650    if flags.contains(InlineContainerStateFlags::IS_SINGLE_LINE_TEXT_INPUT) {
2651        line_height.max_assign(font_metrics.line_gap);
2652    }
2653
2654    line_height
2655}
2656
2657fn effective_baseline_shift(
2658    style: &ComputedValues,
2659    container: Option<&InlineContainerState>,
2660) -> BaselineShift {
2661    if container.is_none() {
2662        // If we are at the root of the inline formatting context, we shouldn't use the
2663        // computed `baseline-shift`, since it has no effect on the contents of this IFC
2664        // (it can just affect how the block container is aligned within the parent IFC).
2665        BaselineShift::zero()
2666    } else {
2667        style.clone_baseline_shift()
2668    }
2669}
2670
2671fn is_baseline_relative(baseline_shift: BaselineShift) -> bool {
2672    !matches!(
2673        baseline_shift,
2674        BaselineShift::Keyword(
2675            BaselineShiftKeyword::Top | BaselineShiftKeyword::Bottom | BaselineShiftKeyword::Center
2676        )
2677    )
2678}
2679
2680/// Whether or not a strut should be created for an inline container. Normally
2681/// all inline containers get struts. In quirks mode this isn't always the case
2682/// though.
2683///
2684/// From <https://quirks.spec.whatwg.org/#the-line-height-calculation-quirk>
2685///
2686/// > ### § 3.3. The line height calculation quirk
2687/// > In quirks mode and limited-quirks mode, an inline box that matches the following
2688/// > conditions, must, for the purpose of line height calculation, act as if the box had a
2689/// > line-height of zero.
2690/// >
2691/// >  - The border-top-width, border-bottom-width, padding-top and padding-bottom
2692/// >    properties have a used value of zero and the box has a vertical writing mode, or the
2693/// >    border-right-width, border-left-width, padding-right and padding-left properties have
2694/// >    a used value of zero and the box has a horizontal writing mode.
2695/// >  - It either contains no text or it contains only collapsed whitespace.
2696/// >
2697/// > ### § 3.4. The blocks ignore line-height quirk
2698/// > In quirks mode and limited-quirks mode, for a block container element whose content is
2699/// > composed of inline-level elements, the element’s line-height must be ignored for the
2700/// > purpose of calculating the minimal height of line boxes within the element.
2701///
2702/// Since we incorporate the size of the strut into the line-height calculation when
2703/// adding text, we can simply not incorporate the strut at the start of inline box
2704/// processing. This also works the same for the root of the IFC.
2705fn inline_container_needs_strut(
2706    style: &ComputedValues,
2707    layout_context: &LayoutContext,
2708    pbm: Option<&PaddingBorderMargin>,
2709) -> bool {
2710    if layout_context.style_context.quirks_mode() == QuirksMode::NoQuirks {
2711        return true;
2712    }
2713
2714    // This is not in a standard yet, but all browsers disable this quirk for list items.
2715    // See https://github.com/whatwg/quirks/issues/38.
2716    if style.get_box().display.is_list_item() {
2717        return true;
2718    }
2719
2720    pbm.is_some_and(|pbm| !pbm.padding_border_sums.inline.is_zero())
2721}
2722
2723impl ComputeInlineContentSizes for InlineFormattingContext {
2724    // This works on an already-constructed `InlineFormattingContext`,
2725    // Which would have to change if/when
2726    // `BlockContainer::construct` parallelize their construction.
2727    fn compute_inline_content_sizes(
2728        &self,
2729        layout_context: &LayoutContext,
2730        constraint_space: &ConstraintSpace,
2731    ) -> InlineContentSizesResult {
2732        ContentSizesComputation::compute(self, layout_context, constraint_space)
2733    }
2734}
2735
2736/// A struct which takes care of computing [`ContentSizes`] for an [`InlineFormattingContext`].
2737struct ContentSizesComputation<'layout_data> {
2738    layout_context: &'layout_data LayoutContext<'layout_data>,
2739    constraint_space: &'layout_data ConstraintSpace<'layout_data>,
2740    paragraph: ContentSizes,
2741    current_line: ContentSizes,
2742    /// Size for whitespace pending to be added to this line.
2743    pending_whitespace: ContentSizes,
2744    /// The size of the not yet cleared floats in the inline axis of the containing block.
2745    uncleared_floats: LogicalSides1D<ContentSizes>,
2746    /// The size of the already cleared floats in the inline axis of the containing block.
2747    cleared_floats: LogicalSides1D<ContentSizes>,
2748    /// Whether or not the current line has seen any content (excluding collapsed whitespace),
2749    /// when sizing under a min-content constraint.
2750    had_content_yet_for_min_content: bool,
2751    /// Whether or not the current line has seen any content (excluding collapsed whitespace),
2752    /// when sizing under a max-content constraint.
2753    had_content_yet_for_max_content: bool,
2754    /// Stack of ending padding, margin, and border to add to the length
2755    /// when an inline box finishes.
2756    ending_inline_pbm_stack: Vec<Au>,
2757    /// Whether the inline content size depends on block constraints.
2758    depends_on_block_constraints: bool,
2759}
2760
2761impl<'layout_data> ContentSizesComputation<'layout_data> {
2762    fn traverse(
2763        mut self,
2764        inline_formatting_context: &InlineFormattingContext,
2765    ) -> InlineContentSizesResult {
2766        self.add_inline_size(
2767            inline_formatting_context.inline_start_for_first_line(self.constraint_space.into()),
2768        );
2769        for inline_item in &inline_formatting_context.inline_items {
2770            self.process_item(inline_item, inline_formatting_context);
2771        }
2772        self.forced_line_break();
2773        self.flush_floats();
2774
2775        InlineContentSizesResult {
2776            sizes: self.paragraph,
2777            depends_on_block_constraints: self.depends_on_block_constraints,
2778        }
2779    }
2780
2781    fn process_item(
2782        &mut self,
2783        inline_item: &InlineItem,
2784        inline_formatting_context: &InlineFormattingContext,
2785    ) {
2786        match inline_item {
2787            InlineItem::StartInlineBox(inline_box) => {
2788                // For margins and paddings, a cyclic percentage is resolved against zero
2789                // for determining intrinsic size contributions.
2790                // https://drafts.csswg.org/css-sizing-3/#min-percentage-contribution
2791                let inline_box = inline_box.borrow();
2792                let zero = Au::zero();
2793                let writing_mode = self.constraint_space.style.writing_mode;
2794                let layout_style = inline_box.layout_style();
2795                let padding = layout_style
2796                    .padding(writing_mode)
2797                    .percentages_relative_to(zero);
2798                let border = layout_style.border_width(writing_mode);
2799                let margin = inline_box
2800                    .base
2801                    .style
2802                    .margin(writing_mode)
2803                    .percentages_relative_to(zero)
2804                    .auto_is(Au::zero);
2805
2806                let pbm = margin + padding + border;
2807                self.add_inline_size(pbm.inline_start);
2808                self.ending_inline_pbm_stack.push(pbm.inline_end);
2809            },
2810            InlineItem::EndInlineBox => {
2811                let length = self.ending_inline_pbm_stack.pop().unwrap_or_else(Au::zero);
2812                self.add_inline_size(length);
2813            },
2814            InlineItem::TextRun(text_run) => {
2815                let text_run = &*text_run.borrow();
2816                let parent_style = text_run.inline_styles.style.borrow();
2817                for item in text_run.items.iter() {
2818                    match item {
2819                        TextRunItem::LineBreak { .. } => {
2820                            // If this run is a forced line break, we *must* break the line
2821                            // and start measuring from the inline origin once more.
2822                            self.forced_line_break();
2823                        },
2824                        TextRunItem::Tab { .. } => {
2825                            self.process_preserved_tab(&parent_style, inline_formatting_context)
2826                        },
2827                        TextRunItem::TextSegment(segment) => {
2828                            self.process_text_segment(&parent_style, segment)
2829                        },
2830                    }
2831                }
2832            },
2833            InlineItem::Atomic(atomic, offset_in_text, _level) => {
2834                // TODO: need to handle TextWrapMode::Nowrap.
2835                if self.had_content_yet_for_min_content &&
2836                    !inline_formatting_context
2837                        .previous_character_prevents_soft_wrap_opportunity(*offset_in_text)
2838                {
2839                    self.line_break_opportunity();
2840                }
2841
2842                self.commit_pending_whitespace();
2843                let outer = self.outer_inline_content_sizes_of_float_or_atomic(&atomic.borrow());
2844                self.current_line += outer;
2845
2846                // TODO: need to handle TextWrapMode::Nowrap.
2847                if !inline_formatting_context
2848                    .next_character_prevents_soft_wrap_opportunity(*offset_in_text)
2849                {
2850                    self.line_break_opportunity();
2851                }
2852            },
2853            InlineItem::OutOfFlowFloatBox(float_box) => {
2854                let float_box = float_box.borrow();
2855                let sizes = self.outer_inline_content_sizes_of_float_or_atomic(&float_box.contents);
2856                let style = &float_box.contents.style();
2857                let container_writing_mode = self.constraint_space.style.writing_mode;
2858                let clear =
2859                    Clear::from_style_and_container_writing_mode(style, container_writing_mode);
2860                self.clear_floats(clear);
2861                let float_side =
2862                    FloatSide::from_style_and_container_writing_mode(style, container_writing_mode);
2863                match float_side.expect("A float box needs to float to some side") {
2864                    FloatSide::InlineStart => self.uncleared_floats.start.union_assign(&sizes),
2865                    FloatSide::InlineEnd => self.uncleared_floats.end.union_assign(&sizes),
2866                }
2867            },
2868            InlineItem::BlockLevel(block_level) => {
2869                self.forced_line_break();
2870                self.flush_floats();
2871                let inline_content_sizes_result =
2872                    compute_inline_content_sizes_for_block_level_boxes(
2873                        std::slice::from_ref(block_level),
2874                        self.layout_context,
2875                        &self.constraint_space.into(),
2876                    );
2877                self.depends_on_block_constraints |=
2878                    inline_content_sizes_result.depends_on_block_constraints;
2879                self.current_line = inline_content_sizes_result.sizes;
2880                self.forced_line_break();
2881            },
2882            InlineItem::OutOfFlowAbsolutelyPositionedBox(..) => {},
2883        }
2884    }
2885
2886    fn process_text_segment(
2887        &mut self,
2888        parent_style: &AtomicRef<'_, ServoArc<ComputedValues>>,
2889        segment: &TextRunSegment,
2890    ) {
2891        let style_text = parent_style.get_inherited_text();
2892        let can_wrap = style_text.text_wrap_mode == TextWrapMode::Wrap;
2893
2894        // TODO: This should take account whether or not the first and last character prevent
2895        // linebreaks after atomics as in layout.
2896        let break_at_start = segment.break_at_start && self.had_content_yet_for_min_content;
2897
2898        for (run_index, run) in segment.runs.iter().enumerate() {
2899            // Break before each unbreakable run in this TextRun, except the first unless the
2900            // linebreaker was set to break before the first run.
2901            if can_wrap && (run_index != 0 || break_at_start) {
2902                self.line_break_opportunity();
2903            }
2904
2905            let advance = run.total_advance();
2906            if run.is_whitespace() {
2907                if !matches!(
2908                    style_text.white_space_collapse,
2909                    WhiteSpaceCollapse::Preserve | WhiteSpaceCollapse::BreakSpaces
2910                ) {
2911                    if self.had_content_yet_for_min_content {
2912                        if can_wrap {
2913                            self.line_break_opportunity();
2914                        } else {
2915                            self.pending_whitespace.min_content += advance;
2916                        }
2917                    }
2918                    if self.had_content_yet_for_max_content {
2919                        self.pending_whitespace.max_content += advance;
2920                    }
2921                    continue;
2922                }
2923                if can_wrap {
2924                    self.pending_whitespace.max_content += advance;
2925                    self.commit_pending_whitespace();
2926                    self.line_break_opportunity();
2927                    continue;
2928                }
2929            }
2930
2931            self.commit_pending_whitespace();
2932            self.add_inline_size(advance);
2933
2934            // Typically whitespace glyphs are placed in a separate store,
2935            // but for `white-space: break-spaces` we place the first whitespace
2936            // with the preceding text. That prevents a line break before that
2937            // first space, but we still need to allow a line break after it.
2938            if can_wrap && run.ends_with_whitespace() {
2939                self.line_break_opportunity();
2940            }
2941        }
2942    }
2943
2944    fn process_preserved_tab(
2945        &mut self,
2946        parent_style: &AtomicRef<'_, ServoArc<ComputedValues>>,
2947        inline_formatting_context: &InlineFormattingContext,
2948    ) {
2949        // If there is a preserved tab, that means that all whitespace is preserved.
2950        self.commit_pending_whitespace();
2951
2952        self.current_line.min_content += inline_formatting_context
2953            .next_tab_stop_after_inline_advance(self.current_line.min_content);
2954        self.current_line.max_content += inline_formatting_context
2955            .next_tab_stop_after_inline_advance(self.current_line.max_content);
2956        if parent_style.get_inherited_text().text_wrap_mode == TextWrapMode::Wrap {
2957            self.line_break_opportunity();
2958        }
2959    }
2960
2961    fn add_inline_size(&mut self, l: Au) {
2962        self.current_line.min_content += l;
2963        self.current_line.max_content += l;
2964    }
2965
2966    fn line_break_opportunity(&mut self) {
2967        // Clear the pending whitespace, assuming that at the end of the line
2968        // it needs to either hang or be removed. If that isn't the case,
2969        // `commit_pending_whitespace()` should be called first.
2970        self.pending_whitespace.min_content = Au::zero();
2971        let current_min_content = mem::take(&mut self.current_line.min_content);
2972        self.paragraph.min_content.max_assign(current_min_content);
2973        self.had_content_yet_for_min_content = false;
2974    }
2975
2976    fn forced_line_break(&mut self) {
2977        // Handle the line break for min-content sizes.
2978        self.line_break_opportunity();
2979
2980        // Repeat the same logic, but now for max-content sizes.
2981        self.pending_whitespace.max_content = Au::zero();
2982        let current_max_content = mem::take(&mut self.current_line.max_content);
2983        self.paragraph.max_content.max_assign(current_max_content);
2984        self.had_content_yet_for_max_content = false;
2985    }
2986
2987    fn commit_pending_whitespace(&mut self) {
2988        self.current_line += mem::take(&mut self.pending_whitespace);
2989        self.had_content_yet_for_min_content = true;
2990        self.had_content_yet_for_max_content = true;
2991    }
2992
2993    fn outer_inline_content_sizes_of_float_or_atomic(
2994        &mut self,
2995        context: &IndependentFormattingContext,
2996    ) -> ContentSizes {
2997        let result = context.outer_inline_content_sizes(
2998            self.layout_context,
2999            &self.constraint_space.into(),
3000            &LogicalVec2::zero(),
3001            false, /* auto_block_size_stretches_to_containing_block */
3002        );
3003        self.depends_on_block_constraints |= result.depends_on_block_constraints;
3004        result.sizes
3005    }
3006
3007    fn clear_floats(&mut self, clear: Clear) {
3008        match clear {
3009            Clear::InlineStart => {
3010                let start_floats = mem::take(&mut self.uncleared_floats.start);
3011                self.cleared_floats.start.max_assign(start_floats);
3012            },
3013            Clear::InlineEnd => {
3014                let end_floats = mem::take(&mut self.uncleared_floats.end);
3015                self.cleared_floats.end.max_assign(end_floats);
3016            },
3017            Clear::Both => {
3018                let start_floats = mem::take(&mut self.uncleared_floats.start);
3019                let end_floats = mem::take(&mut self.uncleared_floats.end);
3020                self.cleared_floats.start.max_assign(start_floats);
3021                self.cleared_floats.end.max_assign(end_floats);
3022            },
3023            Clear::None => {},
3024        }
3025    }
3026
3027    fn flush_floats(&mut self) {
3028        self.clear_floats(Clear::Both);
3029        let start_floats = mem::take(&mut self.cleared_floats.start);
3030        let end_floats = mem::take(&mut self.cleared_floats.end);
3031        self.paragraph.union_assign(&start_floats);
3032        self.paragraph.union_assign(&end_floats);
3033    }
3034
3035    /// Compute the [`ContentSizes`] of the given [`InlineFormattingContext`].
3036    fn compute(
3037        inline_formatting_context: &InlineFormattingContext,
3038        layout_context: &'layout_data LayoutContext,
3039        constraint_space: &'layout_data ConstraintSpace,
3040    ) -> InlineContentSizesResult {
3041        Self {
3042            layout_context,
3043            constraint_space,
3044            paragraph: ContentSizes::zero(),
3045            current_line: ContentSizes::zero(),
3046            pending_whitespace: ContentSizes::zero(),
3047            uncleared_floats: LogicalSides1D::default(),
3048            cleared_floats: LogicalSides1D::default(),
3049            had_content_yet_for_min_content: false,
3050            had_content_yet_for_max_content: false,
3051            ending_inline_pbm_stack: Vec::new(),
3052            depends_on_block_constraints: false,
3053        }
3054        .traverse(inline_formatting_context)
3055    }
3056}
3057
3058/// Whether or not this character will rpevent a soft wrap opportunity when it
3059/// comes before or after an atomic inline element.
3060///
3061/// From <https://www.w3.org/TR/css-text-3/#line-break-details>:
3062///
3063/// > For Web-compatibility there is a soft wrap opportunity before and after each
3064/// > replaced element or other atomic inline, even when adjacent to a character that
3065/// > would normally suppress them, including U+00A0 NO-BREAK SPACE. However, with
3066/// > the exception of U+00A0 NO-BREAK SPACE, there must be no soft wrap opportunity
3067/// > between atomic inlines and adjacent characters belonging to the Unicode GL, WJ,
3068/// > or ZWJ line breaking classes.
3069fn char_prevents_soft_wrap_opportunity_when_before_or_after_atomic(character: char) -> bool {
3070    if character == '\u{00A0}' {
3071        return false;
3072    }
3073    matches!(
3074        icu_properties::maps::line_break().get(character),
3075        ICULineBreak::Glue | ICULineBreak::WordJoiner | ICULineBreak::ZWJ
3076    )
3077}