layout/flow/inline/
mod.rs

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