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