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