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