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