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