Skip to main content

layout/flow/inline/
mod.rs

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